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