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