Skip to main content

gpt/
lib.rs

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