Skip to main content

gpt/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Context as _, Error, anyhow};
6use block_client::{BlockClient, BufferSlice, MutableBufferSlice, RemoteBlockClient};
7use fuchsia_sync::Mutex;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10use zerocopy::{FromBytes as _, IntoBytes as _};
11
12pub mod format;
13
14/// GPT GUIDs are stored in mixed-endian format (see Appendix A of the EFI spec).  To ensure this is
15/// correctly handled, wrap the Uuid type to hide methods that use the UUIDs inappropriately.
16#[derive(Clone, Default, Debug)]
17pub struct Guid(uuid::Uuid);
18
19impl From<uuid::Uuid> for Guid {
20    fn from(uuid: uuid::Uuid) -> Self {
21        Self(uuid)
22    }
23}
24
25impl Guid {
26    pub fn from_bytes(bytes: [u8; 16]) -> Self {
27        Self(uuid::Uuid::from_bytes_le(bytes))
28    }
29
30    pub fn to_bytes(&self) -> [u8; 16] {
31        self.0.to_bytes_le()
32    }
33
34    pub fn to_string(&self) -> String {
35        self.0.to_string()
36    }
37
38    pub fn nil() -> Self {
39        Self(uuid::Uuid::nil())
40    }
41
42    pub fn generate() -> Self {
43        Self(uuid::Uuid::new_v4())
44    }
45}
46
47#[derive(Clone, Debug)]
48pub struct PartitionInfo {
49    pub label: String,
50    pub type_guid: Guid,
51    pub instance_guid: Guid,
52    pub start_block: u64,
53    pub num_blocks: u64,
54    pub flags: u64,
55}
56
57impl PartitionInfo {
58    pub fn from_entry(entry: &format::PartitionTableEntry) -> Result<Self, Error> {
59        let label = String::from_utf16(entry.name.split(|v| *v == 0u16).next().unwrap())?;
60        Ok(Self {
61            label,
62            type_guid: Guid::from_bytes(entry.type_guid),
63            instance_guid: Guid::from_bytes(entry.instance_guid),
64            start_block: entry.first_lba,
65            num_blocks: entry
66                .last_lba
67                .checked_add(1)
68                .unwrap()
69                .checked_sub(entry.first_lba)
70                .unwrap(),
71            flags: entry.flags,
72        })
73    }
74
75    pub fn as_entry(&self) -> format::PartitionTableEntry {
76        let mut name = [0u16; 36];
77        let raw = self.label.encode_utf16().collect::<Vec<_>>();
78        assert!(raw.len() <= name.len());
79        name[..raw.len()].copy_from_slice(&raw[..]);
80        format::PartitionTableEntry {
81            type_guid: self.type_guid.to_bytes(),
82            instance_guid: self.instance_guid.to_bytes(),
83            first_lba: self.start_block,
84            last_lba: self.start_block + self.num_blocks.saturating_sub(1),
85            flags: self.flags,
86            name,
87        }
88    }
89
90    pub fn nil() -> Self {
91        Self {
92            label: String::default(),
93            type_guid: Guid::default(),
94            instance_guid: Guid::default(),
95            start_block: 0,
96            num_blocks: 0,
97            flags: 0,
98        }
99    }
100
101    pub fn is_nil(&self) -> bool {
102        self.label == ""
103            && self.type_guid.0.is_nil()
104            && self.instance_guid.0.is_nil()
105            && self.start_block == 0
106            && self.num_blocks == 0
107            && self.flags == 0
108    }
109}
110
111enum WhichHeader {
112    Primary,
113    Backup,
114}
115
116impl WhichHeader {
117    fn offset(&self, block_size: u64, block_count: u64) -> u64 {
118        match self {
119            Self::Primary => block_size,
120            Self::Backup => (block_count - 1) * block_size,
121        }
122    }
123}
124
125async fn load_metadata(
126    client: &RemoteBlockClient,
127    which: WhichHeader,
128) -> Result<(format::Header, BTreeMap<u32, PartitionInfo>), Error> {
129    let bs = client.block_size() as usize;
130    let mut header_block = vec![0u8; client.block_size() as usize];
131    client
132        .read_at(
133            MutableBufferSlice::Memory(&mut header_block[..]),
134            which.offset(bs as u64, client.block_count() as u64),
135        )
136        .await
137        .context("Read header")?;
138    let (header, _) = format::Header::ref_from_prefix(&header_block[..])
139        .map_err(|_| anyhow!("Header has invalid size"))?;
140    header.ensure_integrity(client.block_count(), client.block_size() as u64)?;
141    let partition_table_offset = header.part_start * bs as u64;
142    let partition_table_size = (header.num_parts * header.part_size) as usize;
143    let partition_table_size_rounded = partition_table_size
144        .checked_next_multiple_of(bs)
145        .ok_or_else(|| anyhow!("Overflow when rounding up partition table size "))?;
146    let mut partition_table = BTreeMap::new();
147    if header.num_parts > 0 {
148        let mut partition_table_blocks = vec![0u8; partition_table_size_rounded];
149        client
150            .read_at(
151                MutableBufferSlice::Memory(&mut partition_table_blocks[..]),
152                partition_table_offset,
153            )
154            .await
155            .with_context(|| {
156                format!(
157                    "Failed to read partition table (sz {}) from offset {}",
158                    partition_table_size, partition_table_offset
159                )
160            })?;
161        let crc = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC)
162            .checksum(&partition_table_blocks[..partition_table_size]);
163        anyhow::ensure!(header.crc32_parts == crc, "Invalid partition table checksum");
164
165        let mut used_ranges = Vec::new();
166        for i in 0..header.num_parts as usize {
167            let entry_raw = &partition_table_blocks
168                [i * header.part_size as usize..(i + 1) * header.part_size as usize];
169            let (entry, _) = format::PartitionTableEntry::ref_from_prefix(entry_raw)
170                .map_err(|_| anyhow!("Failed to parse partition {i}"))?;
171            if entry.is_empty() {
172                continue;
173            }
174            entry
175                .ensure_integrity(header.first_usable, header.last_usable)
176                .context("GPT partition table entry invalid!")?;
177            used_ranges.push(entry.first_lba..entry.last_lba.checked_add(1).unwrap());
178
179            partition_table.insert(i as u32, PartitionInfo::from_entry(entry)?);
180        }
181        used_ranges.sort_by_key(|r| r.start);
182        for pairs in used_ranges.windows(2) {
183            anyhow::ensure!(pairs[0].end <= pairs[1].start, "Overlapping partitions");
184        }
185    }
186    Ok((header.clone(), partition_table))
187}
188
189struct TransactionState {
190    pending_id: u64,
191    next_id: u64,
192}
193
194impl Default for TransactionState {
195    fn default() -> Self {
196        Self { pending_id: u64::MAX, next_id: 0 }
197    }
198}
199
200/// Manages a connection to a GPT-formatted block device.
201pub struct Gpt {
202    client: Arc<RemoteBlockClient>,
203    header: format::Header,
204    partitions: BTreeMap<u32, PartitionInfo>,
205    transaction_state: Arc<Mutex<TransactionState>>,
206}
207
208impl std::fmt::Debug for Gpt {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
210        f.debug_struct("Gpt")
211            .field("header", &self.header)
212            .field("partitions", &self.partitions)
213            .finish()
214    }
215}
216
217#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
218pub enum TransactionCommitError {
219    #[error("I/O error")]
220    Io,
221    #[error("Invalid arguments")]
222    InvalidArguments,
223    #[error("No space")]
224    NoSpace,
225}
226
227impl From<format::FormatError> for TransactionCommitError {
228    fn from(error: format::FormatError) -> Self {
229        match error {
230            format::FormatError::InvalidArguments => Self::InvalidArguments,
231            format::FormatError::NoSpace => Self::NoSpace,
232        }
233    }
234}
235
236impl From<TransactionCommitError> for zx::Status {
237    fn from(error: TransactionCommitError) -> zx::Status {
238        match error {
239            TransactionCommitError::Io => zx::Status::IO,
240            TransactionCommitError::InvalidArguments => zx::Status::INVALID_ARGS,
241            TransactionCommitError::NoSpace => zx::Status::NO_SPACE,
242        }
243    }
244}
245
246#[derive(Eq, thiserror::Error, Clone, Debug, PartialEq)]
247pub enum AddPartitionError {
248    #[error("Invalid arguments")]
249    InvalidArguments,
250    #[error("No space")]
251    NoSpace,
252}
253
254impl From<AddPartitionError> for zx::Status {
255    fn from(error: AddPartitionError) -> zx::Status {
256        match error {
257            AddPartitionError::InvalidArguments => zx::Status::INVALID_ARGS,
258            AddPartitionError::NoSpace => zx::Status::NO_SPACE,
259        }
260    }
261}
262
263impl Gpt {
264    /// Loads and validates a GPT-formatted block device.
265    pub async fn open(client: Arc<RemoteBlockClient>) -> Result<Self, Error> {
266        let mut restore_primary = false;
267        let (header, partitions) = match load_metadata(&client, WhichHeader::Primary).await {
268            Ok(v) => v,
269            Err(error) => {
270                log::warn!(error:?; "Failed to load primary metadata; falling back to backup");
271                restore_primary = true;
272                load_metadata(&client, WhichHeader::Backup)
273                    .await
274                    .context("Failed to load backup metadata")?
275            }
276        };
277        let mut this = Self {
278            client,
279            header,
280            partitions,
281            transaction_state: Arc::new(Mutex::new(TransactionState::default())),
282        };
283        if restore_primary {
284            log::info!("Restoring primary metadata from backup!");
285            this.header.backup_lba = this.header.current_lba;
286            this.header.current_lba = 1;
287            this.header.part_start = 2;
288            this.header.crc32 = this.header.compute_checksum();
289            let partition_table =
290                this.flattened_partitions().into_iter().map(|v| v.as_entry()).collect::<Vec<_>>();
291            let partition_table_raw = format::serialize_partition_table(
292                &mut this.header,
293                this.client.block_size() as usize,
294                this.client.block_count(),
295                &partition_table[..],
296            )
297            .context("Failed to serialize existing partition table")?;
298            this.write_metadata(&this.header, &partition_table_raw[..])
299                .await
300                .context("Failed to restore primary metadata")?;
301        }
302        Ok(this)
303    }
304
305    /// Formats `client` as a new GPT with `partitions`.  Overwrites any existing GPT on the block
306    /// device.
307    pub async fn format(
308        client: Arc<RemoteBlockClient>,
309        partitions: Vec<PartitionInfo>,
310    ) -> Result<Self, Error> {
311        let header = format::Header::new(
312            client.block_count(),
313            client.block_size(),
314            partitions.len() as u32,
315        )?;
316        let mut this = Self {
317            client,
318            header,
319            partitions: BTreeMap::new(),
320            transaction_state: Arc::new(Mutex::new(TransactionState::default())),
321        };
322        let mut transaction = this.create_transaction().unwrap();
323        transaction.partitions = partitions;
324        this.commit_transaction(transaction).await?;
325        Ok(this)
326    }
327
328    pub fn client(&self) -> &Arc<RemoteBlockClient> {
329        &self.client
330    }
331
332    #[cfg(test)]
333    fn take_client(self) -> Arc<RemoteBlockClient> {
334        self.client
335    }
336
337    pub fn header(&self) -> &format::Header {
338        &self.header
339    }
340
341    pub fn partitions(&self) -> &BTreeMap<u32, PartitionInfo> {
342        &self.partitions
343    }
344
345    // We only store valid partitions in memory.  This function allows us to flatten this back out
346    // to a non-sparse array for serialization.
347    fn flattened_partitions(&self) -> Vec<PartitionInfo> {
348        let mut partitions = vec![PartitionInfo::nil(); self.header.num_parts as usize];
349        for (idx, partition) in &self.partitions {
350            partitions[*idx as usize] = partition.clone();
351        }
352        partitions
353    }
354
355    /// Returns None if there's already a pending transaction.
356    pub fn create_transaction(&self) -> Option<Transaction> {
357        {
358            let mut state = self.transaction_state.lock();
359            if state.pending_id != u64::MAX {
360                return None;
361            } else {
362                state.pending_id = state.next_id;
363                state.next_id += 1;
364            }
365        }
366        Some(Transaction {
367            partitions: self.flattened_partitions(),
368            transaction_state: self.transaction_state.clone(),
369        })
370    }
371
372    pub async fn commit_transaction(
373        &mut self,
374        mut transaction: Transaction,
375    ) -> Result<(), TransactionCommitError> {
376        let mut new_header = self.header.clone();
377        let entries =
378            transaction.partitions.iter().map(|entry| entry.as_entry()).collect::<Vec<_>>();
379        let partition_table_raw = format::serialize_partition_table(
380            &mut new_header,
381            self.client.block_size() as usize,
382            self.client.block_count(),
383            &entries[..],
384        )?;
385
386        let mut backup_header = new_header.clone();
387        backup_header.current_lba = backup_header.backup_lba;
388        backup_header.backup_lba = 1;
389        backup_header.part_start = backup_header.last_usable + 1;
390        backup_header.crc32 = backup_header.compute_checksum();
391
392        // Per section 5.3.2 of the UEFI spec, the backup metadata must be written first.  The spec
393        // permits the partition table entries and header to be written in either order.
394        self.write_metadata(&backup_header, &partition_table_raw[..]).await.map_err(|err| {
395            log::warn!(err:?; "Failed to write metadata");
396            TransactionCommitError::Io
397        })?;
398        // NB: It would be preferable to use a barrier here, but not all drivers support barriers at
399        // this time.
400        // TODO(https://fxbug.dev/416348380): Use a barrier between writing secondary/primary.
401        self.client.flush().await.map_err(|err| {
402            log::warn!(err:?; "Failed to flush metadata writes");
403            TransactionCommitError::Io
404        })?;
405        self.write_metadata(&new_header, &partition_table_raw[..]).await.map_err(|err| {
406            log::warn!(err:?; "Failed to write metadata");
407            TransactionCommitError::Io
408        })?;
409        self.client.flush().await.map_err(|err| {
410            log::warn!(err:?; "Failed to flush metadata writes");
411            TransactionCommitError::Io
412        })?;
413
414        self.header = new_header;
415        self.partitions = BTreeMap::new();
416        let mut idx = 0;
417        for partition in std::mem::take(&mut transaction.partitions) {
418            if !partition.is_nil() {
419                self.partitions.insert(idx, partition);
420            }
421            idx += 1;
422        }
423        Ok(())
424    }
425
426    /// Adds a partition in `transaction`.  `info.start_block` must be unset and will be dynamically
427    /// chosen in a first-fit manner.
428    /// The indedx of the partition in the table is returned on success.
429    pub fn add_partition(
430        &mut self,
431        transaction: &mut Transaction,
432        mut info: PartitionInfo,
433    ) -> Result<usize, AddPartitionError> {
434        assert_eq!(info.start_block, 0);
435
436        if info.label.is_empty()
437            || info.type_guid.0.is_nil()
438            || info.instance_guid.0.is_nil()
439            || info.num_blocks == 0
440        {
441            return Err(AddPartitionError::InvalidArguments);
442        }
443
444        let mut allocated_ranges = vec![
445            0..self.header.first_usable,
446            self.header.last_usable + 1..self.client.block_count(),
447        ];
448        let mut slot_idx = None;
449        for i in 0..transaction.partitions.len() {
450            let partition = &transaction.partitions[i];
451            if slot_idx.is_none() && partition.is_nil() {
452                slot_idx = Some(i);
453            }
454            if !partition.is_nil() {
455                allocated_ranges
456                    .push(partition.start_block..partition.start_block + partition.num_blocks);
457            }
458        }
459        let slot_idx = slot_idx.ok_or(AddPartitionError::NoSpace)?;
460        allocated_ranges.sort_by_key(|range| range.start);
461
462        let mut start_block = None;
463        for [a, b] in allocated_ranges.array_windows() {
464            if b.start - a.end >= info.num_blocks {
465                start_block = Some(a.end);
466                break;
467            }
468        }
469        info.start_block = start_block.ok_or(AddPartitionError::NoSpace)?;
470
471        transaction.partitions[slot_idx] = info;
472        Ok(slot_idx)
473    }
474
475    async fn write_metadata(
476        &self,
477        header: &format::Header,
478        partition_table: &[u8],
479    ) -> Result<(), Error> {
480        let bs = self.client.block_size() as usize;
481        let mut header_block = vec![0u8; bs];
482        header.write_to_prefix(&mut header_block[..]).unwrap();
483        self.client
484            .write_at(BufferSlice::Memory(&header_block[..]), header.current_lba * bs as u64)
485            .await
486            .context("Failed to write header")?;
487        if !partition_table.is_empty() {
488            self.client
489                .write_at(BufferSlice::Memory(partition_table), header.part_start * bs as u64)
490                .await
491                .context("Failed to write partition table")?;
492        }
493        Ok(())
494    }
495}
496
497/// Pending changes to the GPT.
498pub struct Transaction {
499    pub partitions: Vec<PartitionInfo>,
500    transaction_state: Arc<Mutex<TransactionState>>,
501}
502
503impl std::fmt::Debug for Transaction {
504    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
505        f.debug_struct("Transaction").field("partitions", &self.partitions).finish()
506    }
507}
508
509impl Drop for Transaction {
510    fn drop(&mut self) {
511        let mut state = self.transaction_state.lock();
512        debug_assert!(state.pending_id != u64::MAX);
513        state.pending_id = u64::MAX;
514    }
515}
516
517#[cfg(test)]
518mod tests {
519    use crate::{AddPartitionError, Gpt, Guid, PartitionInfo, format};
520    use anyhow::Error;
521    use block_client::{BlockClient as _, BufferSlice, MutableBufferSlice, RemoteBlockClient};
522    use fidl_fuchsia_storage_block as fblock;
523    use fuchsia_async as fasync;
524    use std::ops::Range;
525    use std::sync::Arc;
526    use std::sync::atomic::{AtomicBool, Ordering};
527    use test_vmo_backed_block_server::{
528        InitialContents, Observer, VmoBackedServer, VmoBackedServerOptions, WriteAction, WriteCache,
529    };
530    use zerocopy::IntoBytes as _;
531
532    async fn connect_to_server(
533        server: VmoBackedServer,
534    ) -> (Arc<RemoteBlockClient>, fasync::Task<()>) {
535        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
536        let task =
537            fasync::Task::spawn(
538                async move { server.serve(server_end.into_stream()).await.unwrap() },
539            );
540        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
541        (client, task)
542    }
543
544    #[fuchsia::test]
545    async fn load_unformatted_gpt() {
546        let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
547        let (client, _task) = connect_to_server(server).await;
548        Gpt::open(client).await.expect_err("load should fail");
549    }
550
551    #[fuchsia::test]
552    async fn load_formatted_empty_gpt() {
553        let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
554        let (client, _task) = connect_to_server(server).await;
555        Gpt::format(client.clone(), vec![]).await.expect("format failed");
556        Gpt::open(client).await.expect("load should succeed");
557    }
558
559    #[fuchsia::test]
560    async fn load_formatted_gpt_with_minimal_size() {
561        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
562        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
563        const PART_NAME: &str = "part";
564
565        let server = VmoBackedServer::new(6, 4096, &[]).expect("Failed to create VmoBackedServer");
566        let (client, _task) = connect_to_server(server).await;
567        Gpt::format(
568            client.clone(),
569            vec![PartitionInfo {
570                label: PART_NAME.to_string(),
571                type_guid: Guid::from_bytes(PART_TYPE_GUID),
572                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
573                start_block: 3,
574                num_blocks: 1,
575                flags: 0,
576            }],
577        )
578        .await
579        .expect("format failed");
580        let manager = Gpt::open(client).await.expect("load should succeed");
581        assert_eq!(manager.header.first_usable, 3);
582        assert_eq!(manager.header.last_usable, 3);
583        let partition = manager.partitions().get(&0).expect("No entry found");
584        assert_eq!(partition.start_block, 3);
585        assert_eq!(partition.num_blocks, 1);
586        assert!(manager.partitions().get(&1).is_none());
587    }
588
589    #[fuchsia::test]
590    async fn load_formatted_gpt_with_one_partition() {
591        const PART_TYPE_GUID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
592        const PART_INSTANCE_GUID: [u8; 16] =
593            [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
594        const PART_NAME: &str = "part";
595
596        let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
597        let (client, _task) = connect_to_server(server).await;
598        Gpt::format(
599            client.clone(),
600            vec![PartitionInfo {
601                label: PART_NAME.to_string(),
602                type_guid: Guid::from_bytes(PART_TYPE_GUID),
603                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
604                start_block: 4,
605                num_blocks: 1,
606                flags: 0,
607            }],
608        )
609        .await
610        .expect("format failed");
611        let manager = Gpt::open(client).await.expect("load should succeed");
612        let partition = manager.partitions().get(&0).expect("No entry found");
613        assert_eq!(partition.label, "part");
614        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
615        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
616        assert_eq!(partition.start_block, 4);
617        assert_eq!(partition.num_blocks, 1);
618        assert!(manager.partitions().get(&1).is_none());
619    }
620
621    #[fuchsia::test]
622    async fn load_formatted_gpt_with_two_partitions() {
623        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
624        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
625        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
626        const PART_1_NAME: &str = "part1";
627        const PART_2_NAME: &str = "part2";
628
629        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
630        let (client, _task) = connect_to_server(server).await;
631        Gpt::format(
632            client.clone(),
633            vec![
634                PartitionInfo {
635                    label: PART_1_NAME.to_string(),
636                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
637                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
638                    start_block: 4,
639                    num_blocks: 1,
640                    flags: 0,
641                },
642                PartitionInfo {
643                    label: PART_2_NAME.to_string(),
644                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
645                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
646                    start_block: 7,
647                    num_blocks: 1,
648                    flags: 0,
649                },
650            ],
651        )
652        .await
653        .expect("format failed");
654        let manager = Gpt::open(client).await.expect("load should succeed");
655        let partition = manager.partitions().get(&0).expect("No entry found");
656        assert_eq!(partition.label, PART_1_NAME);
657        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
658        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
659        assert_eq!(partition.start_block, 4);
660        assert_eq!(partition.num_blocks, 1);
661        let partition = manager.partitions().get(&1).expect("No entry found");
662        assert_eq!(partition.label, PART_2_NAME);
663        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
664        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
665        assert_eq!(partition.start_block, 7);
666        assert_eq!(partition.num_blocks, 1);
667        assert!(manager.partitions().get(&2).is_none());
668    }
669
670    #[fuchsia::test]
671    async fn load_formatted_gpt_with_extra_bytes_in_partition_name() {
672        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
673        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
674        const PART_NAME: &str = "part\0extrastuff";
675
676        let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
677        let (client, _task) = connect_to_server(server).await;
678        Gpt::format(
679            client.clone(),
680            vec![PartitionInfo {
681                label: PART_NAME.to_string(),
682                type_guid: Guid::from_bytes(PART_TYPE_GUID),
683                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
684                start_block: 4,
685                num_blocks: 1,
686                flags: 0,
687            }],
688        )
689        .await
690        .expect("format failed");
691        let manager = Gpt::open(client).await.expect("load should succeed");
692        let partition = manager.partitions().get(&0).expect("No entry found");
693        // The name should have everything after the first nul byte stripped.
694        assert_eq!(partition.label, "part");
695    }
696
697    #[fuchsia::test]
698    async fn load_formatted_gpt_with_empty_partition_name() {
699        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
700        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
701        const PART_NAME: &str = "";
702
703        let server = VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer");
704        let (client, _task) = connect_to_server(server).await;
705        Gpt::format(
706            client.clone(),
707            vec![PartitionInfo {
708                label: PART_NAME.to_string(),
709                type_guid: Guid::from_bytes(PART_TYPE_GUID),
710                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
711                start_block: 4,
712                num_blocks: 1,
713                flags: 0,
714            }],
715        )
716        .await
717        .expect("format failed");
718        let manager = Gpt::open(client).await.expect("load should succeed");
719        let partition = manager.partitions().get(&0).expect("No entry found");
720        assert_eq!(partition.label, "");
721    }
722
723    #[fuchsia::test]
724    async fn load_formatted_gpt_with_invalid_primary_header() {
725        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
726        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
727        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
728        const PART_1_NAME: &str = "part1";
729        const PART_2_NAME: &str = "part2";
730
731        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
732        let (client, _task) = connect_to_server(server).await;
733        Gpt::format(
734            client.clone(),
735            vec![
736                PartitionInfo {
737                    label: PART_1_NAME.to_string(),
738                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
739                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
740                    start_block: 4,
741                    num_blocks: 1,
742                    flags: 0,
743                },
744                PartitionInfo {
745                    label: PART_2_NAME.to_string(),
746                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
747                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
748                    start_block: 7,
749                    num_blocks: 1,
750                    flags: 0,
751                },
752            ],
753        )
754        .await
755        .expect("format failed");
756        // Clobber the primary header.  The backup should allow the GPT to be used.
757        client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
758        let manager = Gpt::open(client).await.expect("load should succeed");
759        let partition = manager.partitions().get(&0).expect("No entry found");
760        assert_eq!(partition.label, PART_1_NAME);
761        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
762        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
763        assert_eq!(partition.start_block, 4);
764        assert_eq!(partition.num_blocks, 1);
765        let partition = manager.partitions().get(&1).expect("No entry found");
766        assert_eq!(partition.label, PART_2_NAME);
767        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
768        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
769        assert_eq!(partition.start_block, 7);
770        assert_eq!(partition.num_blocks, 1);
771        assert!(manager.partitions().get(&2).is_none());
772    }
773
774    #[fuchsia::test]
775    async fn load_formatted_gpt_with_invalid_primary_partition_table() {
776        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
777        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
778        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
779        const PART_1_NAME: &str = "part1";
780        const PART_2_NAME: &str = "part2";
781
782        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
783        let (client, _task) = connect_to_server(server).await;
784        Gpt::format(
785            client.clone(),
786            vec![
787                PartitionInfo {
788                    label: PART_1_NAME.to_string(),
789                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
790                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
791                    start_block: 4,
792                    num_blocks: 1,
793                    flags: 0,
794                },
795                PartitionInfo {
796                    label: PART_2_NAME.to_string(),
797                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
798                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
799                    start_block: 7,
800                    num_blocks: 1,
801                    flags: 0,
802                },
803            ],
804        )
805        .await
806        .expect("format failed");
807        // Clobber the primary partition table.  The backup should allow the GPT to be used.
808        client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
809        let manager = Gpt::open(client).await.expect("load should succeed");
810        let partition = manager.partitions().get(&0).expect("No entry found");
811        assert_eq!(partition.label, PART_1_NAME);
812        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
813        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
814        assert_eq!(partition.start_block, 4);
815        assert_eq!(partition.num_blocks, 1);
816        let partition = manager.partitions().get(&1).expect("No entry found");
817        assert_eq!(partition.label, PART_2_NAME);
818        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
819        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
820        assert_eq!(partition.start_block, 7);
821        assert_eq!(partition.num_blocks, 1);
822        assert!(manager.partitions().get(&2).is_none());
823    }
824
825    #[fuchsia::test]
826    async fn drop_transaction() {
827        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
828        let (client, _task) = connect_to_server(server).await;
829        Gpt::format(client.clone(), vec![]).await.expect("format failed");
830        let manager = Gpt::open(client).await.expect("load should succeed");
831        {
832            let _transaction = manager.create_transaction().unwrap();
833            assert!(manager.create_transaction().is_none());
834        }
835        let _transaction =
836            manager.create_transaction().expect("Transaction dropped but not available");
837    }
838
839    #[fuchsia::test]
840    async fn commit_empty_transaction() {
841        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
842        let (client, _task) = connect_to_server(server).await;
843        Gpt::format(client.clone(), vec![]).await.expect("format failed");
844        let mut manager = Gpt::open(client).await.expect("load should succeed");
845        let transaction = manager.create_transaction().unwrap();
846        manager.commit_transaction(transaction).await.expect("Commit failed");
847
848        // Check state before and after a reload, to ensure both the in-memory and on-disk
849        // representation match.
850        assert_eq!(manager.header().num_parts, 0);
851        assert!(manager.partitions().is_empty());
852        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
853        assert_eq!(manager.header().num_parts, 0);
854        assert!(manager.partitions().is_empty());
855    }
856
857    #[fuchsia::test]
858    async fn add_partition_in_transaction() {
859        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
860        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
861        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
862        const PART_1_NAME: &str = "part1";
863        const PART_2_NAME: &str = "part2";
864
865        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
866        let (client, _task) = connect_to_server(server).await;
867        Gpt::format(
868            client.clone(),
869            vec![PartitionInfo {
870                label: PART_1_NAME.to_string(),
871                type_guid: Guid::from_bytes(PART_TYPE_GUID),
872                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
873                start_block: 4,
874                num_blocks: 1,
875                flags: 0,
876            }],
877        )
878        .await
879        .expect("format failed");
880        let mut manager = Gpt::open(client).await.expect("load should succeed");
881        let mut transaction = manager.create_transaction().unwrap();
882        assert_eq!(transaction.partitions.len(), 1);
883        transaction.partitions.push(crate::PartitionInfo {
884            label: PART_2_NAME.to_string(),
885            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
886            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
887            start_block: 7,
888            num_blocks: 1,
889            flags: 0,
890        });
891        manager.commit_transaction(transaction).await.expect("Commit failed");
892
893        // Check state before and after a reload, to ensure both the in-memory and on-disk
894        // representation match.
895        assert_eq!(manager.header().num_parts, 2);
896        assert!(manager.partitions().get(&2).is_none());
897        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
898        assert_eq!(manager.header().num_parts, 2);
899        let partition = manager.partitions().get(&0).expect("No entry found");
900        assert_eq!(partition.label, PART_1_NAME);
901        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
902        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
903        assert_eq!(partition.start_block, 4);
904        assert_eq!(partition.num_blocks, 1);
905        let partition = manager.partitions().get(&1).expect("No entry found");
906        assert_eq!(partition.label, PART_2_NAME);
907        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
908        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
909        assert_eq!(partition.start_block, 7);
910        assert_eq!(partition.num_blocks, 1);
911        assert!(manager.partitions().get(&2).is_none());
912    }
913
914    #[fuchsia::test]
915    async fn remove_partition_in_transaction() {
916        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
917        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
918        const PART_NAME: &str = "part1";
919
920        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
921        let (client, _task) = connect_to_server(server).await;
922        Gpt::format(
923            client.clone(),
924            vec![PartitionInfo {
925                label: PART_NAME.to_string(),
926                type_guid: Guid::from_bytes(PART_TYPE_GUID),
927                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
928                start_block: 4,
929                num_blocks: 1,
930                flags: 0,
931            }],
932        )
933        .await
934        .expect("format failed");
935        let mut manager = Gpt::open(client).await.expect("load should succeed");
936        let mut transaction = manager.create_transaction().unwrap();
937        assert_eq!(transaction.partitions.len(), 1);
938        transaction.partitions.clear();
939        manager.commit_transaction(transaction).await.expect("Commit failed");
940
941        // Check state before and after a reload, to ensure both the in-memory and on-disk
942        // representation match.
943        assert_eq!(manager.header().num_parts, 0);
944        assert!(manager.partitions().get(&0).is_none());
945        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
946        assert_eq!(manager.header().num_parts, 0);
947        assert!(manager.partitions().get(&0).is_none());
948    }
949
950    #[fuchsia::test]
951    async fn modify_partition_in_transaction() {
952        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
953        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
954        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
955        const PART_1_NAME: &str = "part1";
956        const PART_2_NAME: &str = "part2";
957
958        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
959        let (client, _task) = connect_to_server(server).await;
960        Gpt::format(
961            client.clone(),
962            vec![PartitionInfo {
963                label: PART_1_NAME.to_string(),
964                type_guid: Guid::from_bytes(PART_TYPE_GUID),
965                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
966                start_block: 4,
967                num_blocks: 1,
968                flags: 0,
969            }],
970        )
971        .await
972        .expect("format failed");
973        let mut manager = Gpt::open(client).await.expect("load should succeed");
974        let mut transaction = manager.create_transaction().unwrap();
975        assert_eq!(transaction.partitions.len(), 1);
976        transaction.partitions[0] = crate::PartitionInfo {
977            label: PART_2_NAME.to_string(),
978            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
979            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
980            start_block: 7,
981            num_blocks: 1,
982            flags: 0,
983        };
984        manager.commit_transaction(transaction).await.expect("Commit failed");
985
986        // Check state before and after a reload, to ensure both the in-memory and on-disk
987        // representation match.
988        assert_eq!(manager.header().num_parts, 1);
989        let partition = manager.partitions().get(&0).expect("No entry found");
990        assert_eq!(partition.label, PART_2_NAME);
991        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
992        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
993        assert_eq!(partition.start_block, 7);
994        assert_eq!(partition.num_blocks, 1);
995        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
996        assert_eq!(manager.header().num_parts, 1);
997        let partition = manager.partitions().get(&0).expect("No entry found");
998        assert_eq!(partition.label, PART_2_NAME);
999        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1000        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1001        assert_eq!(partition.start_block, 7);
1002        assert_eq!(partition.num_blocks, 1);
1003        assert!(manager.partitions().get(&1).is_none());
1004    }
1005
1006    #[fuchsia::test]
1007    async fn grow_partition_table_in_transaction() {
1008        let server =
1009            VmoBackedServer::new(2048, 512, &[]).expect("Failed to create VmoBackedServer");
1010        let (client, _task) = connect_to_server(server).await;
1011        Gpt::format(
1012            client.clone(),
1013            vec![PartitionInfo {
1014                label: "part".to_string(),
1015                type_guid: Guid::from_bytes([1u8; 16]),
1016                instance_guid: Guid::from_bytes([1u8; 16]),
1017                start_block: 34,
1018                num_blocks: 1,
1019                flags: 0,
1020            }],
1021        )
1022        .await
1023        .expect("format failed");
1024        let mut manager = Gpt::open(client).await.expect("load should succeed");
1025        assert_eq!(manager.header().num_parts, 1);
1026        assert_eq!(manager.header().first_usable, 3);
1027        let mut transaction = manager.create_transaction().unwrap();
1028        transaction.partitions.resize(128, crate::PartitionInfo::nil());
1029        manager.commit_transaction(transaction).await.expect("Commit failed");
1030
1031        // Check state before and after a reload, to ensure both the in-memory and on-disk
1032        // representation match.
1033        assert_eq!(manager.header().num_parts, 128);
1034        assert_eq!(manager.header().first_usable, 34);
1035        let partition = manager.partitions().get(&0).expect("No entry found");
1036        assert_eq!(partition.label, "part");
1037        assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1038        assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1039        assert_eq!(partition.start_block, 34);
1040        assert_eq!(partition.num_blocks, 1);
1041        assert!(manager.partitions().get(&1).is_none());
1042        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1043        assert_eq!(manager.header().num_parts, 128);
1044        assert_eq!(manager.header().first_usable, 34);
1045        let partition = manager.partitions().get(&0).expect("No entry found");
1046        assert_eq!(partition.label, "part");
1047        assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1048        assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1049        assert_eq!(partition.start_block, 34);
1050        assert_eq!(partition.num_blocks, 1);
1051        assert!(manager.partitions().get(&1).is_none());
1052    }
1053
1054    #[fuchsia::test]
1055    async fn shrink_partition_table_in_transaction() {
1056        let mut partitions = vec![];
1057        for i in 0..128 {
1058            partitions.push(PartitionInfo {
1059                label: format!("part-{i}"),
1060                type_guid: Guid::from_bytes([i as u8 + 1; 16]),
1061                instance_guid: Guid::from_bytes([i as u8 + 1; 16]),
1062                start_block: 34 + i,
1063                num_blocks: 1,
1064                flags: 0,
1065            });
1066        }
1067        let server =
1068            VmoBackedServer::new(2048, 512, &[]).expect("Failed to create VmoBackedServer");
1069        let (client, _task) = connect_to_server(server).await;
1070        Gpt::format(client.clone(), partitions).await.expect("format failed");
1071        let mut manager = Gpt::open(client).await.expect("load should succeed");
1072        assert_eq!(manager.header().num_parts, 128);
1073        assert_eq!(manager.header().first_usable, 34);
1074        let mut transaction = manager.create_transaction().unwrap();
1075        transaction.partitions.clear();
1076        manager.commit_transaction(transaction).await.expect("Commit failed");
1077
1078        // Check state before and after a reload, to ensure both the in-memory and on-disk
1079        // representation match.
1080        assert_eq!(manager.header().num_parts, 0);
1081        assert_eq!(manager.header().first_usable, 2);
1082        assert!(manager.partitions().get(&0).is_none());
1083        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1084        assert_eq!(manager.header().num_parts, 0);
1085        assert_eq!(manager.header().first_usable, 2);
1086        assert!(manager.partitions().get(&0).is_none());
1087    }
1088
1089    #[fuchsia::test]
1090    async fn invalid_transaction_rejected() {
1091        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1092        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1093        const PART_NAME: &str = "part1";
1094
1095        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
1096        let (client, _task) = connect_to_server(server).await;
1097        Gpt::format(
1098            client.clone(),
1099            vec![PartitionInfo {
1100                label: PART_NAME.to_string(),
1101                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1102                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1103                start_block: 4,
1104                num_blocks: 1,
1105                flags: 0,
1106            }],
1107        )
1108        .await
1109        .expect("format failed");
1110        let mut manager = Gpt::open(client).await.expect("load should succeed");
1111        let mut transaction = manager.create_transaction().unwrap();
1112        assert_eq!(transaction.partitions.len(), 1);
1113        // This overlaps with the GPT metadata, so is invalid.
1114        transaction.partitions[0].start_block = 0;
1115        manager.commit_transaction(transaction).await.expect_err("Commit should have failed");
1116
1117        // Ensure nothing changed. Check state before and after a reload, to ensure both the
1118        // in-memory and on-disk representation match.
1119        assert_eq!(manager.header().num_parts, 1);
1120        let partition = manager.partitions().get(&0).expect("No entry found");
1121        assert_eq!(partition.label, PART_NAME);
1122        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1123        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1124        assert_eq!(partition.start_block, 4);
1125        assert_eq!(partition.num_blocks, 1);
1126        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1127        assert_eq!(manager.header().num_parts, 1);
1128        let partition = manager.partitions().get(&0).expect("No entry found");
1129        assert_eq!(partition.label, PART_NAME);
1130        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1131        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1132        assert_eq!(partition.start_block, 4);
1133        assert_eq!(partition.num_blocks, 1);
1134    }
1135
1136    /// An Observer that discards all writes overlapping its range (specified in bytes, not blocks).
1137    struct DiscardingObserver {
1138        block_size: u64,
1139        discard_range: Range<u64>,
1140    }
1141
1142    impl Observer for DiscardingObserver {
1143        fn write(
1144            &self,
1145            device_block_offset: u64,
1146            block_count: u32,
1147            _vmo: &Arc<zx::Vmo>,
1148            _vmo_offset: u64,
1149            _opts: block_server::WriteOptions,
1150        ) -> WriteAction {
1151            let write_range = (device_block_offset * self.block_size)
1152                ..(device_block_offset + block_count as u64) * self.block_size;
1153            if write_range.end <= self.discard_range.start
1154                || write_range.start >= self.discard_range.end
1155            {
1156                WriteAction::Write
1157            } else {
1158                WriteAction::Discard
1159            }
1160        }
1161    }
1162
1163    #[fuchsia::test]
1164    async fn transaction_applied_if_primary_metadata_partially_written() {
1165        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1166        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1167        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1168        const PART_1_NAME: &str = "part1";
1169        const PART_2_NAME: &str = "part2";
1170
1171        let vmo = zx::Vmo::create(8192).unwrap();
1172        let server = VmoBackedServerOptions {
1173            initial_contents: InitialContents::FromVmo(vmo),
1174            block_size: 512,
1175            observer: Some(Box::new(DiscardingObserver {
1176                discard_range: 1024..1536,
1177                block_size: 512,
1178            })),
1179            ..Default::default()
1180        }
1181        .build()
1182        .unwrap();
1183        let (client, _task) = connect_to_server(server).await;
1184        Gpt::format(
1185            client.clone(),
1186            vec![PartitionInfo {
1187                label: PART_1_NAME.to_string(),
1188                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1189                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1190                start_block: 4,
1191                num_blocks: 1,
1192                flags: 0,
1193            }],
1194        )
1195        .await
1196        .expect("format failed");
1197        let mut manager = Gpt::open(client).await.expect("load should succeed");
1198        let mut transaction = manager.create_transaction().unwrap();
1199        transaction.partitions.push(crate::PartitionInfo {
1200            label: PART_2_NAME.to_string(),
1201            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1202            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1203            start_block: 7,
1204            num_blocks: 1,
1205            flags: 0,
1206        });
1207        manager.commit_transaction(transaction).await.expect("Commit failed");
1208
1209        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1210        assert_eq!(manager.header().num_parts, 2);
1211        let partition = manager.partitions().get(&0).expect("No entry found");
1212        assert_eq!(partition.label, PART_1_NAME);
1213        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1214        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1215        assert_eq!(partition.start_block, 4);
1216        assert_eq!(partition.num_blocks, 1);
1217        let partition = manager.partitions().get(&1).expect("No entry found");
1218        assert_eq!(partition.label, PART_2_NAME);
1219        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1220        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1221        assert_eq!(partition.start_block, 7);
1222        assert_eq!(partition.num_blocks, 1);
1223    }
1224
1225    #[fuchsia::test]
1226    async fn transaction_not_applied_if_primary_metadata_not_written() {
1227        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1228        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1229        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1230        const PART_1_NAME: &str = "part1";
1231        const PART_2_NAME: &str = "part2";
1232
1233        let vmo = zx::Vmo::create(8192).unwrap();
1234        let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1235        {
1236            let server =
1237                VmoBackedServer::from_vmo(512, vmo_dup).expect("Failed to create VmoBackedServer");
1238            let (client, _task) = connect_to_server(server).await;
1239            Gpt::format(
1240                client.clone(),
1241                vec![PartitionInfo {
1242                    label: PART_1_NAME.to_string(),
1243                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1244                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1245                    start_block: 4,
1246                    num_blocks: 1,
1247                    flags: 0,
1248                }],
1249            )
1250            .await
1251            .expect("format failed");
1252        }
1253        let server = VmoBackedServerOptions {
1254            initial_contents: InitialContents::FromVmo(vmo),
1255            block_size: 512,
1256            observer: Some(Box::new(DiscardingObserver {
1257                discard_range: 0..2048,
1258                block_size: 512,
1259            })),
1260            ..Default::default()
1261        }
1262        .build()
1263        .unwrap();
1264        let (client, _task) = connect_to_server(server).await;
1265
1266        let mut manager = Gpt::open(client).await.expect("load should succeed");
1267        let mut transaction = manager.create_transaction().unwrap();
1268        transaction.partitions.push(crate::PartitionInfo {
1269            label: PART_2_NAME.to_string(),
1270            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1271            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1272            start_block: 7,
1273            num_blocks: 1,
1274            flags: 0,
1275        });
1276        manager.commit_transaction(transaction).await.expect("Commit failed");
1277
1278        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1279        assert_eq!(manager.header().num_parts, 1);
1280        let partition = manager.partitions().get(&0).expect("No entry found");
1281        assert_eq!(partition.label, PART_1_NAME);
1282        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1283        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1284        assert_eq!(partition.start_block, 4);
1285        assert_eq!(partition.num_blocks, 1);
1286        assert!(manager.partitions().get(&1).is_none());
1287    }
1288
1289    #[fuchsia::test]
1290    async fn transaction_not_applied_if_backup_metadata_partially_written() {
1291        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1292        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1293        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1294        const PART_1_NAME: &str = "part1";
1295        const PART_2_NAME: &str = "part2";
1296
1297        let vmo = zx::Vmo::create(8192).unwrap();
1298        let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1299        {
1300            let server =
1301                VmoBackedServer::from_vmo(512, vmo_dup).expect("Failed to create VmoBackedServer");
1302            let (client, _task) = connect_to_server(server).await;
1303            Gpt::format(
1304                client.clone(),
1305                vec![PartitionInfo {
1306                    label: PART_1_NAME.to_string(),
1307                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1308                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1309                    start_block: 4,
1310                    num_blocks: 1,
1311                    flags: 0,
1312                }],
1313            )
1314            .await
1315            .expect("format failed");
1316        }
1317        let server = VmoBackedServerOptions {
1318            initial_contents: InitialContents::FromVmo(vmo),
1319            block_size: 512,
1320            observer: Some(Box::new(DiscardingObserver {
1321                discard_range: 0..7680,
1322                block_size: 512,
1323            })),
1324            ..Default::default()
1325        }
1326        .build()
1327        .unwrap();
1328        let (client, _task) = connect_to_server(server).await;
1329
1330        let mut manager = Gpt::open(client).await.expect("load should succeed");
1331        let mut transaction = manager.create_transaction().unwrap();
1332        transaction.partitions.push(crate::PartitionInfo {
1333            label: PART_2_NAME.to_string(),
1334            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1335            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1336            start_block: 7,
1337            num_blocks: 1,
1338            flags: 0,
1339        });
1340        manager.commit_transaction(transaction).await.expect("Commit failed");
1341
1342        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1343        assert_eq!(manager.header().num_parts, 1);
1344        let partition = manager.partitions().get(&0).expect("No entry found");
1345        assert_eq!(partition.label, PART_1_NAME);
1346        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1347        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1348        assert_eq!(partition.start_block, 4);
1349        assert_eq!(partition.num_blocks, 1);
1350        assert!(manager.partitions().get(&1).is_none());
1351    }
1352
1353    #[fuchsia::test]
1354    async fn restore_primary_from_backup() {
1355        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1356        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1357        const PART_NAME: &str = "part1";
1358
1359        let server = VmoBackedServer::new(16, 512, &[]).expect("Failed to create VmoBackedServer");
1360        let (client, _task) = connect_to_server(server).await;
1361        Gpt::format(
1362            client.clone(),
1363            vec![PartitionInfo {
1364                label: PART_NAME.to_string(),
1365                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1366                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1367                start_block: 4,
1368                num_blocks: 1,
1369                flags: 0,
1370            }],
1371        )
1372        .await
1373        .expect("format failed");
1374        let mut old_metadata = vec![0u8; 2048];
1375        client.read_at(MutableBufferSlice::Memory(&mut old_metadata[..]), 0).await.unwrap();
1376        let mut buffer = vec![0u8; 2048];
1377        client.write_at(BufferSlice::Memory(&buffer[..]), 0).await.unwrap();
1378
1379        let manager = Gpt::open(client).await.expect("load should succeed");
1380        let client = manager.take_client();
1381
1382        client.read_at(MutableBufferSlice::Memory(&mut buffer[..]), 0).await.unwrap();
1383        assert_eq!(old_metadata, buffer);
1384    }
1385
1386    #[fuchsia::test]
1387    async fn load_golden_gpt_linux() {
1388        let server = VmoBackedServer::from_file(512, "/pkg/data/gpt_golden/gpt.linux.blk");
1389        let (client, _task) = connect_to_server(server).await;
1390        let manager = Gpt::open(client).await.expect("load should succeed");
1391        let partition = manager.partitions().get(&0).expect("No entry found");
1392        assert_eq!(partition.label, "ext");
1393        assert_eq!(partition.type_guid.to_string(), "0fc63daf-8483-4772-8e79-3d69d8477de4");
1394        assert_eq!(partition.start_block, 8);
1395        assert_eq!(partition.num_blocks, 1);
1396        assert!(manager.partitions().get(&1).is_none());
1397    }
1398
1399    #[fuchsia::test]
1400    async fn load_golden_gpt_fuchsia() {
1401        let server = VmoBackedServer::from_file(512, "/pkg/data/gpt_golden/gpt.fuchsia.blk");
1402        let (client, _task) = connect_to_server(server).await;
1403
1404        struct ExpectedPartition {
1405            label: &'static str,
1406            type_guid: &'static str,
1407            blocks: Range<u64>,
1408        }
1409        const EXPECTED_PARTITIONS: [ExpectedPartition; 8] = [
1410            ExpectedPartition {
1411                label: "bootloader",
1412                type_guid: "5ece94fe-4c86-11e8-a15b-480fcf35f8e6",
1413                blocks: 11..12,
1414            },
1415            ExpectedPartition {
1416                label: "zircon_a",
1417                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1418                blocks: 12..13,
1419            },
1420            ExpectedPartition {
1421                label: "zircon_b",
1422                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1423                blocks: 13..14,
1424            },
1425            ExpectedPartition {
1426                label: "zircon_r",
1427                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1428                blocks: 14..15,
1429            },
1430            ExpectedPartition {
1431                label: "vbmeta_a",
1432                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1433                blocks: 15..16,
1434            },
1435            ExpectedPartition {
1436                label: "vbmeta_b",
1437                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1438                blocks: 16..17,
1439            },
1440            ExpectedPartition {
1441                label: "vbmeta_r",
1442                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1443                blocks: 17..18,
1444            },
1445            ExpectedPartition {
1446                label: "durable_boot",
1447                type_guid: "a409e16b-78aa-4acc-995c-302352621a41",
1448                blocks: 18..19,
1449            },
1450        ];
1451
1452        let manager = Gpt::open(client).await.expect("load should succeed");
1453        for i in 0..EXPECTED_PARTITIONS.len() as u32 {
1454            let partition = manager.partitions().get(&i).expect("No entry found");
1455            let expected = &EXPECTED_PARTITIONS[i as usize];
1456            assert_eq!(partition.label, expected.label);
1457            assert_eq!(partition.type_guid.to_string(), expected.type_guid);
1458            assert_eq!(partition.start_block, expected.blocks.start);
1459            assert_eq!(partition.num_blocks, expected.blocks.end - expected.blocks.start);
1460        }
1461    }
1462
1463    #[fuchsia::test]
1464    async fn add_partitions_till_no_blocks_left() {
1465        let server = VmoBackedServer::new(128, 512, &[]).expect("Failed to create VmoBackedServer");
1466        let (client, _task) = connect_to_server(server).await;
1467        Gpt::format(client.clone(), vec![PartitionInfo::nil(); 32]).await.expect("format failed");
1468        let mut manager = Gpt::open(client).await.expect("load should succeed");
1469        let mut transaction = manager.create_transaction().unwrap();
1470        assert_eq!(transaction.partitions.len(), 32);
1471        let mut num = 0;
1472        loop {
1473            match manager.add_partition(
1474                &mut transaction,
1475                crate::PartitionInfo {
1476                    label: format!("part-{num}"),
1477                    type_guid: crate::Guid::generate(),
1478                    instance_guid: crate::Guid::generate(),
1479                    start_block: 0,
1480                    num_blocks: 1,
1481                    flags: 0,
1482                },
1483            ) {
1484                Ok(_) => {
1485                    num += 1;
1486                }
1487                Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1488                Err(AddPartitionError::NoSpace) => break,
1489            };
1490        }
1491        assert!(num <= 32);
1492        manager.commit_transaction(transaction).await.expect("Commit failed");
1493
1494        // Check state before and after a reload, to ensure both the in-memory and on-disk
1495        // representation match.
1496        assert_eq!(manager.header().num_parts, 32);
1497        assert_eq!(manager.partitions().len(), num);
1498
1499        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1500        assert_eq!(manager.header().num_parts, 32);
1501        assert_eq!(manager.partitions().len(), num);
1502    }
1503
1504    #[fuchsia::test]
1505    async fn add_partitions_till_no_slots_left() {
1506        let server = VmoBackedServer::new(128, 512, &[]).expect("Failed to create VmoBackedServer");
1507        let (client, _task) = connect_to_server(server).await;
1508        Gpt::format(client.clone(), vec![PartitionInfo::nil(); 4]).await.expect("format failed");
1509        let mut manager = Gpt::open(client).await.expect("load should succeed");
1510        let mut transaction = manager.create_transaction().unwrap();
1511        assert_eq!(transaction.partitions.len(), 4);
1512        let mut num = 0;
1513        loop {
1514            match manager.add_partition(
1515                &mut transaction,
1516                crate::PartitionInfo {
1517                    label: format!("part-{num}"),
1518                    type_guid: crate::Guid::generate(),
1519                    instance_guid: crate::Guid::generate(),
1520                    start_block: 0,
1521                    num_blocks: 1,
1522                    flags: 0,
1523                },
1524            ) {
1525                Ok(_) => {
1526                    num += 1;
1527                }
1528                Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1529                Err(AddPartitionError::NoSpace) => break,
1530            };
1531        }
1532        assert!(num <= 4);
1533        manager.commit_transaction(transaction).await.expect("Commit failed");
1534
1535        // Check state before and after a reload, to ensure both the in-memory and on-disk
1536        // representation match.
1537        assert_eq!(manager.header().num_parts, 4);
1538        assert_eq!(manager.partitions().len(), num);
1539
1540        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1541        assert_eq!(manager.header().num_parts, 4);
1542        assert_eq!(manager.partitions().len(), num);
1543    }
1544
1545    /// An Observer that shuffles writes and discards some of the tail since last flush.
1546    struct ShufflingObserver {
1547        // Only start shuffling once this is set.
1548        start: Arc<AtomicBool>,
1549        // Only shuffle if there is a write to this offset.
1550        shuffle_if_contains_offset: u64,
1551    }
1552
1553    impl Observer for ShufflingObserver {
1554        fn flush(&self, writes: Option<&mut WriteCache>) {
1555            if self.start.load(Ordering::Relaxed) {
1556                let Some(writes) = writes else { unreachable!() };
1557                if writes
1558                    .iter()
1559                    .filter(|(offset, _)| **offset == self.shuffle_if_contains_offset)
1560                    .next()
1561                    .is_some()
1562                {
1563                    writes.shuffle();
1564                    writes.discard_some();
1565                }
1566            }
1567        }
1568
1569        fn close(&self, writes: Option<&mut WriteCache>) {
1570            // Always shuffle every write which had yet to be flushed when the client closed.
1571            if self.start.load(Ordering::Relaxed) {
1572                let Some(writes) = writes else { unreachable!() };
1573                writes.shuffle();
1574            }
1575        }
1576    }
1577
1578    #[fuchsia::test]
1579    async fn metadata_update_is_atomic() {
1580        const BLOCK_SIZE: u64 = 512;
1581        const BLOCK_COUNT: u64 = 128;
1582        // Test once where we shuffle any set of writes which contains the primary superblock, and
1583        // once where we shuffle any set of writes which contains the secondary superblock.
1584        // The goal is to ensure that writes are correctly sequenced with some sort of flush or
1585        // barrier (secondary, <barrier>, primary), so metadata updates are atomic.
1586        for shuffle_if_contains_offset in [1, BLOCK_COUNT - 1] {
1587            let vmo = zx::Vmo::create(BLOCK_SIZE * BLOCK_COUNT).unwrap();
1588            let start_shuffling = Arc::new(AtomicBool::new(false));
1589            let server = VmoBackedServerOptions {
1590                initial_contents: InitialContents::FromVmo(vmo),
1591                block_size: BLOCK_SIZE as u32,
1592                observer: Some(Box::new(ShufflingObserver {
1593                    start: start_shuffling.clone(),
1594                    shuffle_if_contains_offset,
1595                })),
1596                write_tracking: true,
1597                ..Default::default()
1598            }
1599            .build()
1600            .unwrap();
1601            let (client, _task) = connect_to_server(server).await;
1602            Gpt::format(client.clone(), vec![PartitionInfo::nil(); 80])
1603                .await
1604                .expect("format failed");
1605
1606            start_shuffling.store(true, Ordering::Relaxed);
1607
1608            let mut manager = Gpt::open(client).await.expect("load should succeed");
1609            let mut transaction = manager.create_transaction().unwrap();
1610            transaction.partitions.truncate(40);
1611            let mut num = 0;
1612            loop {
1613                match manager.add_partition(
1614                    &mut transaction,
1615                    crate::PartitionInfo {
1616                        label: format!("part-{num}"),
1617                        type_guid: crate::Guid::generate(),
1618                        instance_guid: crate::Guid::generate(),
1619                        start_block: 0,
1620                        num_blocks: 1,
1621                        flags: 0,
1622                    },
1623                ) {
1624                    Ok(_) => {
1625                        num += 1;
1626                    }
1627                    Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1628                    Err(AddPartitionError::NoSpace) => break,
1629                };
1630            }
1631            assert!(num <= 40);
1632            manager.commit_transaction(transaction).await.expect("Commit failed");
1633
1634            // Check state before and after a reload.
1635            assert_eq!(manager.header().num_parts, 40);
1636            assert_eq!(manager.partitions().len(), num);
1637
1638            // If the GPT implementation has appropriate barriers/flushes between secondary and
1639            // primary metadata updates, then we will end up in either the old state or the new
1640            // state.  Otherwise, both copies might become corrupt and the GPT would be unreadable.
1641            let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1642            let len = manager.partitions().len();
1643            assert!(len == 0 || len == num);
1644        }
1645    }
1646
1647    async fn try_load_invalid_gpt(
1648        block_count: u64,
1649        block_size: u32,
1650        mut header: format::Header,
1651        entries: Vec<format::PartitionTableEntry>,
1652    ) -> Result<Gpt, Error> {
1653        let vmo = zx::Vmo::create(block_count * block_size as u64).unwrap();
1654
1655        let part_size = std::mem::size_of::<format::PartitionTableEntry>();
1656        let mut part_table_bytes = vec![0u8; entries.len() * part_size];
1657        for (i, entry) in entries.iter().enumerate() {
1658            part_table_bytes[i * part_size..(i + 1) * part_size].copy_from_slice(entry.as_bytes());
1659        }
1660
1661        let crc_parts = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC).checksum(&part_table_bytes);
1662        header.crc32_parts = crc_parts;
1663        header.crc32 = header.compute_checksum();
1664
1665        // Write primary
1666        vmo.write(header.as_bytes(), block_size as u64).unwrap();
1667        vmo.write(&part_table_bytes, 2 * block_size as u64).unwrap();
1668
1669        // Write backup
1670        let mut backup_header = header.clone();
1671        backup_header.current_lba = block_count - 1;
1672        backup_header.backup_lba = 1;
1673        backup_header.part_start = backup_header.last_usable + 1;
1674        backup_header.crc32 = backup_header.compute_checksum();
1675
1676        vmo.write(backup_header.as_bytes(), (block_count - 1) * block_size as u64).unwrap();
1677
1678        let partition_table_len = header.part_size as u64 * header.num_parts as u64;
1679        let partition_table_blocks =
1680            partition_table_len.checked_next_multiple_of(block_size as u64).unwrap()
1681                / block_size as u64;
1682
1683        if backup_header.part_start + partition_table_blocks <= backup_header.current_lba {
1684            vmo.write(&part_table_bytes, backup_header.part_start * block_size as u64).unwrap();
1685        }
1686
1687        let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1688        let server = VmoBackedServer::from_vmo(block_size, vmo_clone).unwrap();
1689        let (client, _task) = connect_to_server(server).await;
1690
1691        Gpt::open(client).await
1692    }
1693
1694    #[fuchsia::test]
1695    async fn test_partition_before_first_usable() {
1696        let block_count = 128;
1697        let block_size = 512;
1698        let header = format::Header::new(block_count, block_size, 128).unwrap();
1699        let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1700        entries[0] = format::PartitionTableEntry {
1701            type_guid: [1; 16],
1702            instance_guid: [1; 16],
1703            first_lba: header.first_usable - 1,
1704            last_lba: header.first_usable + 10,
1705            ..format::PartitionTableEntry::empty()
1706        };
1707        let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1708        assert!(res.is_err());
1709        let err_msg = format!("{:?}", res.err().unwrap());
1710        assert!(
1711            err_msg.contains("GPT partition table entry invalid"),
1712            "Unexpected error: {}",
1713            err_msg
1714        );
1715    }
1716
1717    #[fuchsia::test]
1718    async fn test_partition_after_last_usable() {
1719        let block_count = 128;
1720        let block_size = 512;
1721        let header = format::Header::new(block_count, block_size, 128).unwrap();
1722        let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1723        entries[0] = format::PartitionTableEntry {
1724            type_guid: [1; 16],
1725            instance_guid: [1; 16],
1726            first_lba: header.first_usable,
1727            last_lba: header.last_usable + 1,
1728            ..format::PartitionTableEntry::empty()
1729        };
1730        let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1731        assert!(res.is_err());
1732        let err_msg = format!("{:?}", res.err().unwrap());
1733        assert!(
1734            err_msg.contains("GPT partition table entry invalid"),
1735            "Unexpected error: {}",
1736            err_msg
1737        );
1738    }
1739
1740    #[fuchsia::test]
1741    async fn test_overlapping_partitions() {
1742        let block_count = 128;
1743        let block_size = 512;
1744        let header = format::Header::new(block_count, block_size, 128).unwrap();
1745        let mut entries = vec![format::PartitionTableEntry::empty(); 128];
1746        entries[0] = format::PartitionTableEntry {
1747            type_guid: [1; 16],
1748            instance_guid: [1; 16],
1749            first_lba: header.first_usable,
1750            last_lba: header.first_usable + 10,
1751            ..format::PartitionTableEntry::empty()
1752        };
1753        entries[1] = format::PartitionTableEntry {
1754            type_guid: [1; 16],
1755            instance_guid: [2; 16],
1756            first_lba: header.first_usable + 5, // Overlaps
1757            last_lba: header.first_usable + 15,
1758            ..format::PartitionTableEntry::empty()
1759        };
1760        let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1761        assert!(res.is_err());
1762        let err_msg = format!("{:?}", res.err().unwrap());
1763        assert!(err_msg.contains("Overlapping partitions"), "Unexpected error: {}", err_msg);
1764    }
1765
1766    #[fuchsia::test]
1767    async fn test_header_first_usable_too_small() {
1768        let block_count = 128;
1769        let block_size = 512;
1770        let mut header = format::Header::new(block_count, block_size, 128).unwrap();
1771        // partition_table_blocks = 128 * 128 / 512 = 32.
1772        // first_lba = 1.
1773        // We want first_usable = first_lba + partition_table_blocks = 33
1774        // (invalid, overlaps with partition table).
1775        // Valid first_usable is >= 34.
1776        header.first_usable = 33;
1777        let entries = vec![format::PartitionTableEntry::empty(); 128];
1778        let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1779        assert!(res.is_err());
1780        let err_msg = format!("{:?}", res.err().unwrap());
1781        assert!(err_msg.contains("Invalid first_usable"), "Unexpected error: {}", err_msg);
1782    }
1783
1784    #[fuchsia::test]
1785    async fn test_header_last_usable_too_large() {
1786        let block_count = 128;
1787        let block_size = 512;
1788        let mut header = format::Header::new(block_count, block_size, 128).unwrap();
1789        // partition_table_blocks = 32.
1790        // second_lba = 127.
1791        // We want last_usable = 95 (invalid, overlaps with backup partition table starting at 96).
1792        // Valid last_usable is <= 94.
1793        header.last_usable = 95;
1794        let entries = vec![format::PartitionTableEntry::empty(); 128];
1795        let res = try_load_invalid_gpt(block_count, block_size, header, entries).await;
1796        assert!(res.is_err());
1797        let err_msg = format!("{:?}", res.err().unwrap());
1798        assert!(err_msg.contains("Invalid last_usable"), "Unexpected error: {}", err_msg);
1799    }
1800}