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 window in allocated_ranges.windows(2) {
456            if window[1].start - window[0].end >= info.num_blocks {
457                start_block = Some(window[0].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 std::ops::Range;
514    use std::sync::Arc;
515    use std::sync::atomic::{AtomicBool, Ordering};
516    use vmo_backed_block_server::{
517        InitialContents, VmoBackedServer, VmoBackedServerOptions, VmoBackedServerTestingExt as _,
518    };
519    use zx::HandleBased;
520    use {fidl_fuchsia_storage_block as fblock, fuchsia_async as fasync};
521
522    #[fuchsia::test]
523    async fn load_unformatted_gpt() {
524        let vmo = zx::Vmo::create(4096).unwrap();
525        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
526        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
527
528        let _task =
529            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
530        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
531        Gpt::open(client).await.expect_err("load should fail");
532    }
533
534    #[fuchsia::test]
535    async fn load_formatted_empty_gpt() {
536        let vmo = zx::Vmo::create(4096).unwrap();
537        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
538        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
539
540        let _task =
541            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
542        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
543        Gpt::format(client.clone(), vec![]).await.expect("format failed");
544        Gpt::open(client).await.expect("load should succeed");
545    }
546
547    #[fuchsia::test]
548    async fn load_formatted_gpt_with_minimal_size() {
549        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
550        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
551        const PART_NAME: &str = "part";
552
553        let vmo = zx::Vmo::create(6 * 4096).unwrap();
554        let server = Arc::new(VmoBackedServer::from_vmo(4096, vmo));
555        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
556
557        let _task =
558            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
559        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
560        Gpt::format(
561            client.clone(),
562            vec![PartitionInfo {
563                label: PART_NAME.to_string(),
564                type_guid: Guid::from_bytes(PART_TYPE_GUID),
565                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
566                start_block: 3,
567                num_blocks: 1,
568                flags: 0,
569            }],
570        )
571        .await
572        .expect("format failed");
573        let manager = Gpt::open(client).await.expect("load should succeed");
574        assert_eq!(manager.header.first_usable, 3);
575        assert_eq!(manager.header.last_usable, 3);
576        let partition = manager.partitions().get(&0).expect("No entry found");
577        assert_eq!(partition.start_block, 3);
578        assert_eq!(partition.num_blocks, 1);
579        assert!(manager.partitions().get(&1).is_none());
580    }
581
582    #[fuchsia::test]
583    async fn load_formatted_gpt_with_one_partition() {
584        const PART_TYPE_GUID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
585        const PART_INSTANCE_GUID: [u8; 16] =
586            [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31];
587        const PART_NAME: &str = "part";
588
589        let vmo = zx::Vmo::create(4096).unwrap();
590        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
591        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
592
593        let _task =
594            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
595        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
596        Gpt::format(
597            client.clone(),
598            vec![PartitionInfo {
599                label: PART_NAME.to_string(),
600                type_guid: Guid::from_bytes(PART_TYPE_GUID),
601                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
602                start_block: 4,
603                num_blocks: 1,
604                flags: 0,
605            }],
606        )
607        .await
608        .expect("format failed");
609        let manager = Gpt::open(client).await.expect("load should succeed");
610        let partition = manager.partitions().get(&0).expect("No entry found");
611        assert_eq!(partition.label, "part");
612        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
613        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
614        assert_eq!(partition.start_block, 4);
615        assert_eq!(partition.num_blocks, 1);
616        assert!(manager.partitions().get(&1).is_none());
617    }
618
619    #[fuchsia::test]
620    async fn load_formatted_gpt_with_two_partitions() {
621        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
622        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
623        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
624        const PART_1_NAME: &str = "part1";
625        const PART_2_NAME: &str = "part2";
626
627        let vmo = zx::Vmo::create(8192).unwrap();
628        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
629        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
630
631        let _task =
632            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
633        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
634        Gpt::format(
635            client.clone(),
636            vec![
637                PartitionInfo {
638                    label: PART_1_NAME.to_string(),
639                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
640                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
641                    start_block: 4,
642                    num_blocks: 1,
643                    flags: 0,
644                },
645                PartitionInfo {
646                    label: PART_2_NAME.to_string(),
647                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
648                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
649                    start_block: 7,
650                    num_blocks: 1,
651                    flags: 0,
652                },
653            ],
654        )
655        .await
656        .expect("format failed");
657        let manager = Gpt::open(client).await.expect("load should succeed");
658        let partition = manager.partitions().get(&0).expect("No entry found");
659        assert_eq!(partition.label, PART_1_NAME);
660        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
661        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
662        assert_eq!(partition.start_block, 4);
663        assert_eq!(partition.num_blocks, 1);
664        let partition = manager.partitions().get(&1).expect("No entry found");
665        assert_eq!(partition.label, PART_2_NAME);
666        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
667        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
668        assert_eq!(partition.start_block, 7);
669        assert_eq!(partition.num_blocks, 1);
670        assert!(manager.partitions().get(&2).is_none());
671    }
672
673    #[fuchsia::test]
674    async fn load_formatted_gpt_with_extra_bytes_in_partition_name() {
675        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
676        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
677        const PART_NAME: &str = "part\0extrastuff";
678
679        let vmo = zx::Vmo::create(4096).unwrap();
680        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
681        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
682
683        let _task =
684            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
685        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
686        Gpt::format(
687            client.clone(),
688            vec![PartitionInfo {
689                label: PART_NAME.to_string(),
690                type_guid: Guid::from_bytes(PART_TYPE_GUID),
691                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
692                start_block: 4,
693                num_blocks: 1,
694                flags: 0,
695            }],
696        )
697        .await
698        .expect("format failed");
699        let manager = Gpt::open(client).await.expect("load should succeed");
700        let partition = manager.partitions().get(&0).expect("No entry found");
701        // The name should have everything after the first nul byte stripped.
702        assert_eq!(partition.label, "part");
703    }
704
705    #[fuchsia::test]
706    async fn load_formatted_gpt_with_empty_partition_name() {
707        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
708        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
709        const PART_NAME: &str = "";
710
711        let vmo = zx::Vmo::create(4096).unwrap();
712        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
713        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
714
715        let _task =
716            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
717        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
718        Gpt::format(
719            client.clone(),
720            vec![PartitionInfo {
721                label: PART_NAME.to_string(),
722                type_guid: Guid::from_bytes(PART_TYPE_GUID),
723                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
724                start_block: 4,
725                num_blocks: 1,
726                flags: 0,
727            }],
728        )
729        .await
730        .expect("format failed");
731        let manager = Gpt::open(client).await.expect("load should succeed");
732        let partition = manager.partitions().get(&0).expect("No entry found");
733        assert_eq!(partition.label, "");
734    }
735
736    #[fuchsia::test]
737    async fn load_formatted_gpt_with_invalid_primary_header() {
738        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
739        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
740        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
741        const PART_1_NAME: &str = "part1";
742        const PART_2_NAME: &str = "part2";
743
744        let vmo = zx::Vmo::create(8192).unwrap();
745
746        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
747        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
748
749        let _task =
750            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
751        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
752        Gpt::format(
753            client.clone(),
754            vec![
755                PartitionInfo {
756                    label: PART_1_NAME.to_string(),
757                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
758                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
759                    start_block: 4,
760                    num_blocks: 1,
761                    flags: 0,
762                },
763                PartitionInfo {
764                    label: PART_2_NAME.to_string(),
765                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
766                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
767                    start_block: 7,
768                    num_blocks: 1,
769                    flags: 0,
770                },
771            ],
772        )
773        .await
774        .expect("format failed");
775        // Clobber the primary header.  The backup should allow the GPT to be used.
776        client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
777        let manager = Gpt::open(client).await.expect("load should succeed");
778        let partition = manager.partitions().get(&0).expect("No entry found");
779        assert_eq!(partition.label, PART_1_NAME);
780        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
781        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
782        assert_eq!(partition.start_block, 4);
783        assert_eq!(partition.num_blocks, 1);
784        let partition = manager.partitions().get(&1).expect("No entry found");
785        assert_eq!(partition.label, PART_2_NAME);
786        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
787        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
788        assert_eq!(partition.start_block, 7);
789        assert_eq!(partition.num_blocks, 1);
790        assert!(manager.partitions().get(&2).is_none());
791    }
792
793    #[fuchsia::test]
794    async fn load_formatted_gpt_with_invalid_primary_partition_table() {
795        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
796        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
797        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
798        const PART_1_NAME: &str = "part1";
799        const PART_2_NAME: &str = "part2";
800
801        let vmo = zx::Vmo::create(8192).unwrap();
802
803        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
804        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
805
806        let _task =
807            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
808        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
809        Gpt::format(
810            client.clone(),
811            vec![
812                PartitionInfo {
813                    label: PART_1_NAME.to_string(),
814                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
815                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
816                    start_block: 4,
817                    num_blocks: 1,
818                    flags: 0,
819                },
820                PartitionInfo {
821                    label: PART_2_NAME.to_string(),
822                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
823                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
824                    start_block: 7,
825                    num_blocks: 1,
826                    flags: 0,
827                },
828            ],
829        )
830        .await
831        .expect("format failed");
832        // Clobber the primary partition table.  The backup should allow the GPT to be used.
833        client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
834        let manager = Gpt::open(client).await.expect("load should succeed");
835        let partition = manager.partitions().get(&0).expect("No entry found");
836        assert_eq!(partition.label, PART_1_NAME);
837        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
838        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
839        assert_eq!(partition.start_block, 4);
840        assert_eq!(partition.num_blocks, 1);
841        let partition = manager.partitions().get(&1).expect("No entry found");
842        assert_eq!(partition.label, PART_2_NAME);
843        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
844        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
845        assert_eq!(partition.start_block, 7);
846        assert_eq!(partition.num_blocks, 1);
847        assert!(manager.partitions().get(&2).is_none());
848    }
849
850    #[fuchsia::test]
851    async fn drop_transaction() {
852        let vmo = zx::Vmo::create(8192).unwrap();
853        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
854        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
855
856        let _task =
857            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
858        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
859        Gpt::format(client.clone(), vec![]).await.expect("format failed");
860        let manager = Gpt::open(client).await.expect("load should succeed");
861        {
862            let _transaction = manager.create_transaction().unwrap();
863            assert!(manager.create_transaction().is_none());
864        }
865        let _transaction =
866            manager.create_transaction().expect("Transaction dropped but not available");
867    }
868
869    #[fuchsia::test]
870    async fn commit_empty_transaction() {
871        let vmo = zx::Vmo::create(8192).unwrap();
872        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
873        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
874
875        let _task =
876            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
877        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
878        Gpt::format(client.clone(), vec![]).await.expect("format failed");
879        let mut manager = Gpt::open(client).await.expect("load should succeed");
880        let transaction = manager.create_transaction().unwrap();
881        manager.commit_transaction(transaction).await.expect("Commit failed");
882
883        // Check state before and after a reload, to ensure both the in-memory and on-disk
884        // representation match.
885        assert_eq!(manager.header().num_parts, 0);
886        assert!(manager.partitions().is_empty());
887        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
888        assert_eq!(manager.header().num_parts, 0);
889        assert!(manager.partitions().is_empty());
890    }
891
892    #[fuchsia::test]
893    async fn add_partition_in_transaction() {
894        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
895        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
896        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
897        const PART_1_NAME: &str = "part1";
898        const PART_2_NAME: &str = "part2";
899
900        let vmo = zx::Vmo::create(8192).unwrap();
901        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
902        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
903
904        let _task =
905            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
906        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
907        Gpt::format(
908            client.clone(),
909            vec![PartitionInfo {
910                label: PART_1_NAME.to_string(),
911                type_guid: Guid::from_bytes(PART_TYPE_GUID),
912                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
913                start_block: 4,
914                num_blocks: 1,
915                flags: 0,
916            }],
917        )
918        .await
919        .expect("format failed");
920        let mut manager = Gpt::open(client).await.expect("load should succeed");
921        let mut transaction = manager.create_transaction().unwrap();
922        assert_eq!(transaction.partitions.len(), 1);
923        transaction.partitions.push(crate::PartitionInfo {
924            label: PART_2_NAME.to_string(),
925            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
926            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
927            start_block: 7,
928            num_blocks: 1,
929            flags: 0,
930        });
931        manager.commit_transaction(transaction).await.expect("Commit failed");
932
933        // Check state before and after a reload, to ensure both the in-memory and on-disk
934        // representation match.
935        assert_eq!(manager.header().num_parts, 2);
936        assert!(manager.partitions().get(&2).is_none());
937        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
938        assert_eq!(manager.header().num_parts, 2);
939        let partition = manager.partitions().get(&0).expect("No entry found");
940        assert_eq!(partition.label, PART_1_NAME);
941        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
942        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
943        assert_eq!(partition.start_block, 4);
944        assert_eq!(partition.num_blocks, 1);
945        let partition = manager.partitions().get(&1).expect("No entry found");
946        assert_eq!(partition.label, PART_2_NAME);
947        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
948        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
949        assert_eq!(partition.start_block, 7);
950        assert_eq!(partition.num_blocks, 1);
951        assert!(manager.partitions().get(&2).is_none());
952    }
953
954    #[fuchsia::test]
955    async fn remove_partition_in_transaction() {
956        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
957        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
958        const PART_NAME: &str = "part1";
959
960        let vmo = zx::Vmo::create(8192).unwrap();
961        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
962        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
963
964        let _task =
965            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
966        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
967        Gpt::format(
968            client.clone(),
969            vec![PartitionInfo {
970                label: PART_NAME.to_string(),
971                type_guid: Guid::from_bytes(PART_TYPE_GUID),
972                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
973                start_block: 4,
974                num_blocks: 1,
975                flags: 0,
976            }],
977        )
978        .await
979        .expect("format failed");
980        let mut manager = Gpt::open(client).await.expect("load should succeed");
981        let mut transaction = manager.create_transaction().unwrap();
982        assert_eq!(transaction.partitions.len(), 1);
983        transaction.partitions.clear();
984        manager.commit_transaction(transaction).await.expect("Commit failed");
985
986        // Check state before and after a reload, to ensure both the in-memory and on-disk
987        // representation match.
988        assert_eq!(manager.header().num_parts, 0);
989        assert!(manager.partitions().get(&0).is_none());
990        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
991        assert_eq!(manager.header().num_parts, 0);
992        assert!(manager.partitions().get(&0).is_none());
993    }
994
995    #[fuchsia::test]
996    async fn modify_partition_in_transaction() {
997        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
998        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
999        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1000        const PART_1_NAME: &str = "part1";
1001        const PART_2_NAME: &str = "part2";
1002
1003        let vmo = zx::Vmo::create(8192).unwrap();
1004        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1005        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1006
1007        let _task =
1008            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1009        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1010        Gpt::format(
1011            client.clone(),
1012            vec![PartitionInfo {
1013                label: PART_1_NAME.to_string(),
1014                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1015                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1016                start_block: 4,
1017                num_blocks: 1,
1018                flags: 0,
1019            }],
1020        )
1021        .await
1022        .expect("format failed");
1023        let mut manager = Gpt::open(client).await.expect("load should succeed");
1024        let mut transaction = manager.create_transaction().unwrap();
1025        assert_eq!(transaction.partitions.len(), 1);
1026        transaction.partitions[0] = crate::PartitionInfo {
1027            label: PART_2_NAME.to_string(),
1028            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1029            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1030            start_block: 7,
1031            num_blocks: 1,
1032            flags: 0,
1033        };
1034        manager.commit_transaction(transaction).await.expect("Commit failed");
1035
1036        // Check state before and after a reload, to ensure both the in-memory and on-disk
1037        // representation match.
1038        assert_eq!(manager.header().num_parts, 1);
1039        let partition = manager.partitions().get(&0).expect("No entry found");
1040        assert_eq!(partition.label, PART_2_NAME);
1041        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1042        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1043        assert_eq!(partition.start_block, 7);
1044        assert_eq!(partition.num_blocks, 1);
1045        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1046        assert_eq!(manager.header().num_parts, 1);
1047        let partition = manager.partitions().get(&0).expect("No entry found");
1048        assert_eq!(partition.label, PART_2_NAME);
1049        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1050        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1051        assert_eq!(partition.start_block, 7);
1052        assert_eq!(partition.num_blocks, 1);
1053        assert!(manager.partitions().get(&1).is_none());
1054    }
1055
1056    #[fuchsia::test]
1057    async fn grow_partition_table_in_transaction() {
1058        let vmo = zx::Vmo::create(1024 * 1024).unwrap();
1059        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1060        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1061
1062        let _task =
1063            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1064        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1065        Gpt::format(
1066            client.clone(),
1067            vec![PartitionInfo {
1068                label: "part".to_string(),
1069                type_guid: Guid::from_bytes([1u8; 16]),
1070                instance_guid: Guid::from_bytes([1u8; 16]),
1071                start_block: 34,
1072                num_blocks: 1,
1073                flags: 0,
1074            }],
1075        )
1076        .await
1077        .expect("format failed");
1078        let mut manager = Gpt::open(client).await.expect("load should succeed");
1079        assert_eq!(manager.header().num_parts, 1);
1080        assert_eq!(manager.header().first_usable, 3);
1081        let mut transaction = manager.create_transaction().unwrap();
1082        transaction.partitions.resize(128, crate::PartitionInfo::nil());
1083        manager.commit_transaction(transaction).await.expect("Commit failed");
1084
1085        // Check state before and after a reload, to ensure both the in-memory and on-disk
1086        // representation match.
1087        assert_eq!(manager.header().num_parts, 128);
1088        assert_eq!(manager.header().first_usable, 34);
1089        let partition = manager.partitions().get(&0).expect("No entry found");
1090        assert_eq!(partition.label, "part");
1091        assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1092        assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1093        assert_eq!(partition.start_block, 34);
1094        assert_eq!(partition.num_blocks, 1);
1095        assert!(manager.partitions().get(&1).is_none());
1096        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1097        assert_eq!(manager.header().num_parts, 128);
1098        assert_eq!(manager.header().first_usable, 34);
1099        let partition = manager.partitions().get(&0).expect("No entry found");
1100        assert_eq!(partition.label, "part");
1101        assert_eq!(partition.type_guid.to_bytes(), [1u8; 16]);
1102        assert_eq!(partition.instance_guid.to_bytes(), [1u8; 16]);
1103        assert_eq!(partition.start_block, 34);
1104        assert_eq!(partition.num_blocks, 1);
1105        assert!(manager.partitions().get(&1).is_none());
1106    }
1107
1108    #[fuchsia::test]
1109    async fn shrink_partition_table_in_transaction() {
1110        let vmo = zx::Vmo::create(1024 * 1024).unwrap();
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 = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1123        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1124
1125        let _task =
1126            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1127        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1128        Gpt::format(client.clone(), partitions).await.expect("format failed");
1129        let mut manager = Gpt::open(client).await.expect("load should succeed");
1130        assert_eq!(manager.header().num_parts, 128);
1131        assert_eq!(manager.header().first_usable, 34);
1132        let mut transaction = manager.create_transaction().unwrap();
1133        transaction.partitions.clear();
1134        manager.commit_transaction(transaction).await.expect("Commit failed");
1135
1136        // Check state before and after a reload, to ensure both the in-memory and on-disk
1137        // representation match.
1138        assert_eq!(manager.header().num_parts, 0);
1139        assert_eq!(manager.header().first_usable, 2);
1140        assert!(manager.partitions().get(&0).is_none());
1141        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1142        assert_eq!(manager.header().num_parts, 0);
1143        assert_eq!(manager.header().first_usable, 2);
1144        assert!(manager.partitions().get(&0).is_none());
1145    }
1146
1147    #[fuchsia::test]
1148    async fn invalid_transaction_rejected() {
1149        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1150        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1151        const PART_NAME: &str = "part1";
1152
1153        let vmo = zx::Vmo::create(8192).unwrap();
1154        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1155        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1156
1157        let _task =
1158            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1159        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1160        Gpt::format(
1161            client.clone(),
1162            vec![PartitionInfo {
1163                label: PART_NAME.to_string(),
1164                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1165                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1166                start_block: 4,
1167                num_blocks: 1,
1168                flags: 0,
1169            }],
1170        )
1171        .await
1172        .expect("format failed");
1173        let mut manager = Gpt::open(client).await.expect("load should succeed");
1174        let mut transaction = manager.create_transaction().unwrap();
1175        assert_eq!(transaction.partitions.len(), 1);
1176        // This overlaps with the GPT metadata, so is invalid.
1177        transaction.partitions[0].start_block = 0;
1178        manager.commit_transaction(transaction).await.expect_err("Commit should have failed");
1179
1180        // Ensure nothing changed. Check state before and after a reload, to ensure both the
1181        // in-memory and on-disk representation match.
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        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1190        assert_eq!(manager.header().num_parts, 1);
1191        let partition = manager.partitions().get(&0).expect("No entry found");
1192        assert_eq!(partition.label, PART_NAME);
1193        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1194        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_GUID);
1195        assert_eq!(partition.start_block, 4);
1196        assert_eq!(partition.num_blocks, 1);
1197    }
1198
1199    /// An Observer that discards all writes overlapping its range (specified in bytes, not blocks).
1200    struct DiscardingObserver {
1201        block_size: u64,
1202        discard_range: Range<u64>,
1203    }
1204
1205    impl vmo_backed_block_server::Observer for DiscardingObserver {
1206        fn write(
1207            &self,
1208            device_block_offset: u64,
1209            block_count: u32,
1210            _vmo: &Arc<zx::Vmo>,
1211            _vmo_offset: u64,
1212            _opts: block_server::WriteOptions,
1213        ) -> vmo_backed_block_server::WriteAction {
1214            let write_range = (device_block_offset * self.block_size)
1215                ..(device_block_offset + block_count as u64) * self.block_size;
1216            if write_range.end <= self.discard_range.start
1217                || write_range.start >= self.discard_range.end
1218            {
1219                vmo_backed_block_server::WriteAction::Write
1220            } else {
1221                vmo_backed_block_server::WriteAction::Discard
1222            }
1223        }
1224    }
1225
1226    #[fuchsia::test]
1227    async fn transaction_applied_if_primary_metadata_partially_written() {
1228        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1229        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1230        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1231        const PART_1_NAME: &str = "part1";
1232        const PART_2_NAME: &str = "part2";
1233
1234        let vmo = zx::Vmo::create(8192).unwrap();
1235        let server = Arc::new(
1236            VmoBackedServerOptions {
1237                initial_contents: InitialContents::FromVmo(vmo),
1238                block_size: 512,
1239                observer: Some(Box::new(DiscardingObserver {
1240                    discard_range: 1024..1536,
1241                    block_size: 512,
1242                })),
1243                ..Default::default()
1244            }
1245            .build()
1246            .unwrap(),
1247        );
1248        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1249
1250        let _task =
1251            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1252        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1253        Gpt::format(
1254            client.clone(),
1255            vec![PartitionInfo {
1256                label: PART_1_NAME.to_string(),
1257                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1258                instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1259                start_block: 4,
1260                num_blocks: 1,
1261                flags: 0,
1262            }],
1263        )
1264        .await
1265        .expect("format failed");
1266        let mut manager = Gpt::open(client).await.expect("load should succeed");
1267        let mut transaction = manager.create_transaction().unwrap();
1268        transaction.partitions.push(crate::PartitionInfo {
1269            label: PART_2_NAME.to_string(),
1270            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1271            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1272            start_block: 7,
1273            num_blocks: 1,
1274            flags: 0,
1275        });
1276        manager.commit_transaction(transaction).await.expect("Commit failed");
1277
1278        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1279        assert_eq!(manager.header().num_parts, 2);
1280        let partition = manager.partitions().get(&0).expect("No entry found");
1281        assert_eq!(partition.label, PART_1_NAME);
1282        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1283        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1284        assert_eq!(partition.start_block, 4);
1285        assert_eq!(partition.num_blocks, 1);
1286        let partition = manager.partitions().get(&1).expect("No entry found");
1287        assert_eq!(partition.label, PART_2_NAME);
1288        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1289        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_2_GUID);
1290        assert_eq!(partition.start_block, 7);
1291        assert_eq!(partition.num_blocks, 1);
1292    }
1293
1294    #[fuchsia::test]
1295    async fn transaction_not_applied_if_primary_metadata_not_written() {
1296        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1297        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1298        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1299        const PART_1_NAME: &str = "part1";
1300        const PART_2_NAME: &str = "part2";
1301
1302        let vmo = zx::Vmo::create(8192).unwrap();
1303        let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1304        {
1305            let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1306            let server = Arc::new(VmoBackedServer::from_vmo(512, vmo_dup));
1307            let _task =
1308                fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1309            let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1310            Gpt::format(
1311                client.clone(),
1312                vec![PartitionInfo {
1313                    label: PART_1_NAME.to_string(),
1314                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1315                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1316                    start_block: 4,
1317                    num_blocks: 1,
1318                    flags: 0,
1319                }],
1320            )
1321            .await
1322            .expect("format failed");
1323        }
1324        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1325        let server = Arc::new(
1326            VmoBackedServerOptions {
1327                initial_contents: InitialContents::FromVmo(vmo),
1328                block_size: 512,
1329                observer: Some(Box::new(DiscardingObserver {
1330                    discard_range: 0..2048,
1331                    block_size: 512,
1332                })),
1333                ..Default::default()
1334            }
1335            .build()
1336            .unwrap(),
1337        );
1338        let _task =
1339            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1340        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1341
1342        let mut manager = Gpt::open(client).await.expect("load should succeed");
1343        let mut transaction = manager.create_transaction().unwrap();
1344        transaction.partitions.push(crate::PartitionInfo {
1345            label: PART_2_NAME.to_string(),
1346            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1347            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1348            start_block: 7,
1349            num_blocks: 1,
1350            flags: 0,
1351        });
1352        manager.commit_transaction(transaction).await.expect("Commit failed");
1353
1354        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1355        assert_eq!(manager.header().num_parts, 1);
1356        let partition = manager.partitions().get(&0).expect("No entry found");
1357        assert_eq!(partition.label, PART_1_NAME);
1358        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1359        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1360        assert_eq!(partition.start_block, 4);
1361        assert_eq!(partition.num_blocks, 1);
1362        assert!(manager.partitions().get(&1).is_none());
1363    }
1364
1365    #[fuchsia::test]
1366    async fn transaction_not_applied_if_backup_metadata_partially_written() {
1367        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1368        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1369        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1370        const PART_1_NAME: &str = "part1";
1371        const PART_2_NAME: &str = "part2";
1372
1373        let vmo = zx::Vmo::create(8192).unwrap();
1374        let vmo_dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
1375        {
1376            let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1377            let server = Arc::new(VmoBackedServer::from_vmo(512, vmo_dup));
1378            let _task =
1379                fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1380            let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1381            Gpt::format(
1382                client.clone(),
1383                vec![PartitionInfo {
1384                    label: PART_1_NAME.to_string(),
1385                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1386                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1387                    start_block: 4,
1388                    num_blocks: 1,
1389                    flags: 0,
1390                }],
1391            )
1392            .await
1393            .expect("format failed");
1394        }
1395        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1396        let server = Arc::new(
1397            VmoBackedServerOptions {
1398                initial_contents: InitialContents::FromVmo(vmo),
1399                block_size: 512,
1400                observer: Some(Box::new(DiscardingObserver {
1401                    discard_range: 0..7680,
1402                    block_size: 512,
1403                })),
1404                ..Default::default()
1405            }
1406            .build()
1407            .unwrap(),
1408        );
1409        let _task =
1410            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1411        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1412
1413        let mut manager = Gpt::open(client).await.expect("load should succeed");
1414        let mut transaction = manager.create_transaction().unwrap();
1415        transaction.partitions.push(crate::PartitionInfo {
1416            label: PART_2_NAME.to_string(),
1417            type_guid: crate::Guid::from_bytes(PART_TYPE_GUID),
1418            instance_guid: crate::Guid::from_bytes(PART_INSTANCE_2_GUID),
1419            start_block: 7,
1420            num_blocks: 1,
1421            flags: 0,
1422        });
1423        manager.commit_transaction(transaction).await.expect("Commit failed");
1424
1425        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1426        assert_eq!(manager.header().num_parts, 1);
1427        let partition = manager.partitions().get(&0).expect("No entry found");
1428        assert_eq!(partition.label, PART_1_NAME);
1429        assert_eq!(partition.type_guid.to_bytes(), PART_TYPE_GUID);
1430        assert_eq!(partition.instance_guid.to_bytes(), PART_INSTANCE_1_GUID);
1431        assert_eq!(partition.start_block, 4);
1432        assert_eq!(partition.num_blocks, 1);
1433        assert!(manager.partitions().get(&1).is_none());
1434    }
1435
1436    #[fuchsia::test]
1437    async fn restore_primary_from_backup() {
1438        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1439        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1440        const PART_NAME: &str = "part1";
1441
1442        let vmo = zx::Vmo::create(8192).unwrap();
1443        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1444        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1445
1446        let _task =
1447            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1448        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1449        Gpt::format(
1450            client.clone(),
1451            vec![PartitionInfo {
1452                label: PART_NAME.to_string(),
1453                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1454                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1455                start_block: 4,
1456                num_blocks: 1,
1457                flags: 0,
1458            }],
1459        )
1460        .await
1461        .expect("format failed");
1462        let mut old_metadata = vec![0u8; 2048];
1463        client.read_at(MutableBufferSlice::Memory(&mut old_metadata[..]), 0).await.unwrap();
1464        let mut buffer = vec![0u8; 2048];
1465        client.write_at(BufferSlice::Memory(&buffer[..]), 0).await.unwrap();
1466
1467        let manager = Gpt::open(client).await.expect("load should succeed");
1468        let client = manager.take_client();
1469
1470        client.read_at(MutableBufferSlice::Memory(&mut buffer[..]), 0).await.unwrap();
1471        assert_eq!(old_metadata, buffer);
1472    }
1473
1474    #[fuchsia::test]
1475    async fn load_golden_gpt_linux() {
1476        let contents = std::fs::read("/pkg/data/gpt_golden/gpt.linux.blk").unwrap();
1477        let server = Arc::new(VmoBackedServer::new(contents.len() as u64 / 512, 512, &contents));
1478        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1479
1480        let _task =
1481            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1482        let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1483            .await
1484            .expect("load should succeed");
1485        let partition = manager.partitions().get(&0).expect("No entry found");
1486        assert_eq!(partition.label, "ext");
1487        assert_eq!(partition.type_guid.to_string(), "0fc63daf-8483-4772-8e79-3d69d8477de4");
1488        assert_eq!(partition.start_block, 8);
1489        assert_eq!(partition.num_blocks, 1);
1490        assert!(manager.partitions().get(&1).is_none());
1491    }
1492
1493    #[fuchsia::test]
1494    async fn load_golden_gpt_fuchsia() {
1495        let contents = std::fs::read("/pkg/data/gpt_golden/gpt.fuchsia.blk").unwrap();
1496        let server = Arc::new(VmoBackedServer::new(contents.len() as u64 / 512, 512, &contents));
1497        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1498
1499        struct ExpectedPartition {
1500            label: &'static str,
1501            type_guid: &'static str,
1502            blocks: Range<u64>,
1503        }
1504        const EXPECTED_PARTITIONS: [ExpectedPartition; 8] = [
1505            ExpectedPartition {
1506                label: "bootloader",
1507                type_guid: "5ece94fe-4c86-11e8-a15b-480fcf35f8e6",
1508                blocks: 11..12,
1509            },
1510            ExpectedPartition {
1511                label: "zircon_a",
1512                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1513                blocks: 12..13,
1514            },
1515            ExpectedPartition {
1516                label: "zircon_b",
1517                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1518                blocks: 13..14,
1519            },
1520            ExpectedPartition {
1521                label: "zircon_r",
1522                type_guid: "9b37fff6-2e58-466a-983a-f7926d0b04e0",
1523                blocks: 14..15,
1524            },
1525            ExpectedPartition {
1526                label: "vbmeta_a",
1527                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1528                blocks: 15..16,
1529            },
1530            ExpectedPartition {
1531                label: "vbmeta_b",
1532                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1533                blocks: 16..17,
1534            },
1535            ExpectedPartition {
1536                label: "vbmeta_r",
1537                type_guid: "421a8bfc-85d9-4d85-acda-b64eec0133e9",
1538                blocks: 17..18,
1539            },
1540            ExpectedPartition {
1541                label: "durable_boot",
1542                type_guid: "a409e16b-78aa-4acc-995c-302352621a41",
1543                blocks: 18..19,
1544            },
1545        ];
1546
1547        let _task =
1548            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1549        let manager = Gpt::open(Arc::new(RemoteBlockClient::new(client).await.unwrap()))
1550            .await
1551            .expect("load should succeed");
1552        for i in 0..EXPECTED_PARTITIONS.len() as u32 {
1553            let partition = manager.partitions().get(&i).expect("No entry found");
1554            let expected = &EXPECTED_PARTITIONS[i as usize];
1555            assert_eq!(partition.label, expected.label);
1556            assert_eq!(partition.type_guid.to_string(), expected.type_guid);
1557            assert_eq!(partition.start_block, expected.blocks.start);
1558            assert_eq!(partition.num_blocks, expected.blocks.end - expected.blocks.start);
1559        }
1560    }
1561
1562    #[fuchsia::test]
1563    async fn add_partitions_till_no_blocks_left() {
1564        let vmo = zx::Vmo::create(65536).unwrap();
1565        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1566        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1567
1568        let _task =
1569            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1570        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1571        Gpt::format(client.clone(), vec![PartitionInfo::nil(); 32]).await.expect("format failed");
1572        let mut manager = Gpt::open(client).await.expect("load should succeed");
1573        let mut transaction = manager.create_transaction().unwrap();
1574        assert_eq!(transaction.partitions.len(), 32);
1575        let mut num = 0;
1576        loop {
1577            match manager.add_partition(
1578                &mut transaction,
1579                crate::PartitionInfo {
1580                    label: format!("part-{num}"),
1581                    type_guid: crate::Guid::generate(),
1582                    instance_guid: crate::Guid::generate(),
1583                    start_block: 0,
1584                    num_blocks: 1,
1585                    flags: 0,
1586                },
1587            ) {
1588                Ok(_) => {
1589                    num += 1;
1590                }
1591                Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1592                Err(AddPartitionError::NoSpace) => break,
1593            };
1594        }
1595        assert!(num <= 32);
1596        manager.commit_transaction(transaction).await.expect("Commit failed");
1597
1598        // Check state before and after a reload, to ensure both the in-memory and on-disk
1599        // representation match.
1600        assert_eq!(manager.header().num_parts, 32);
1601        assert_eq!(manager.partitions().len(), num);
1602
1603        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1604        assert_eq!(manager.header().num_parts, 32);
1605        assert_eq!(manager.partitions().len(), num);
1606    }
1607
1608    #[fuchsia::test]
1609    async fn add_partitions_till_no_slots_left() {
1610        let vmo = zx::Vmo::create(65536).unwrap();
1611        let server = Arc::new(VmoBackedServer::from_vmo(512, vmo));
1612        let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1613
1614        let _task =
1615            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1616        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1617        Gpt::format(client.clone(), vec![PartitionInfo::nil(); 4]).await.expect("format failed");
1618        let mut manager = Gpt::open(client).await.expect("load should succeed");
1619        let mut transaction = manager.create_transaction().unwrap();
1620        assert_eq!(transaction.partitions.len(), 4);
1621        let mut num = 0;
1622        loop {
1623            match manager.add_partition(
1624                &mut transaction,
1625                crate::PartitionInfo {
1626                    label: format!("part-{num}"),
1627                    type_guid: crate::Guid::generate(),
1628                    instance_guid: crate::Guid::generate(),
1629                    start_block: 0,
1630                    num_blocks: 1,
1631                    flags: 0,
1632                },
1633            ) {
1634                Ok(_) => {
1635                    num += 1;
1636                }
1637                Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1638                Err(AddPartitionError::NoSpace) => break,
1639            };
1640        }
1641        assert!(num <= 4);
1642        manager.commit_transaction(transaction).await.expect("Commit failed");
1643
1644        // Check state before and after a reload, to ensure both the in-memory and on-disk
1645        // representation match.
1646        assert_eq!(manager.header().num_parts, 4);
1647        assert_eq!(manager.partitions().len(), num);
1648
1649        let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1650        assert_eq!(manager.header().num_parts, 4);
1651        assert_eq!(manager.partitions().len(), num);
1652    }
1653
1654    /// An Observer that shuffles writes and discards some of the tail since last flush.
1655    struct ShufflingObserver {
1656        // Only start shuffling once this is set.
1657        start: Arc<AtomicBool>,
1658        // Only shuffle if there is a write to this offset.
1659        shuffle_if_contains_offset: u64,
1660    }
1661
1662    impl vmo_backed_block_server::Observer for ShufflingObserver {
1663        fn flush(&self, writes: Option<&mut vmo_backed_block_server::WriteCache>) {
1664            if self.start.load(Ordering::Relaxed) {
1665                let Some(writes) = writes else { unreachable!() };
1666                if writes
1667                    .iter()
1668                    .filter(|(offset, _)| **offset == self.shuffle_if_contains_offset)
1669                    .next()
1670                    .is_some()
1671                {
1672                    writes.shuffle();
1673                    writes.discard_some();
1674                }
1675            }
1676        }
1677
1678        fn close(&self, writes: Option<&mut vmo_backed_block_server::WriteCache>) {
1679            // Always shuffle every write which had yet to be flushed when the client closed.
1680            if self.start.load(Ordering::Relaxed) {
1681                let Some(writes) = writes else { unreachable!() };
1682                writes.shuffle();
1683            }
1684        }
1685    }
1686
1687    #[fuchsia::test]
1688    async fn metadata_update_is_atomic() {
1689        const BLOCK_SIZE: u64 = 512;
1690        const BLOCK_COUNT: u64 = 128;
1691        // Test once where we shuffle any set of writes which contains the primary superblock, and
1692        // once where we shuffle any set of writes which contains the secondary superblock.
1693        // The goal is to ensure that writes are correctly sequenced with some sort of flush or
1694        // barrier (secondary, <barrier>, primary), so metadata updates are atomic.
1695        for shuffle_if_contains_offset in [1, BLOCK_COUNT - 1] {
1696            let vmo = zx::Vmo::create(BLOCK_SIZE * BLOCK_COUNT).unwrap();
1697            let start_shuffling = Arc::new(AtomicBool::new(false));
1698            let server = Arc::new(
1699                VmoBackedServerOptions {
1700                    initial_contents: InitialContents::FromVmo(vmo),
1701                    block_size: BLOCK_SIZE as u32,
1702                    observer: Some(Box::new(ShufflingObserver {
1703                        start: start_shuffling.clone(),
1704                        shuffle_if_contains_offset,
1705                    })),
1706                    write_tracking: true,
1707                    ..Default::default()
1708                }
1709                .build()
1710                .unwrap(),
1711            );
1712            let (client, server_end) = fidl::endpoints::create_proxy::<fblock::BlockMarker>();
1713            let _task =
1714                fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
1715            let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
1716            Gpt::format(client.clone(), vec![PartitionInfo::nil(); 80])
1717                .await
1718                .expect("format failed");
1719
1720            start_shuffling.store(true, Ordering::Relaxed);
1721
1722            let mut manager = Gpt::open(client).await.expect("load should succeed");
1723            let mut transaction = manager.create_transaction().unwrap();
1724            transaction.partitions.truncate(40);
1725            let mut num = 0;
1726            loop {
1727                match manager.add_partition(
1728                    &mut transaction,
1729                    crate::PartitionInfo {
1730                        label: format!("part-{num}"),
1731                        type_guid: crate::Guid::generate(),
1732                        instance_guid: crate::Guid::generate(),
1733                        start_block: 0,
1734                        num_blocks: 1,
1735                        flags: 0,
1736                    },
1737                ) {
1738                    Ok(_) => {
1739                        num += 1;
1740                    }
1741                    Err(AddPartitionError::InvalidArguments) => panic!("Unexpected error"),
1742                    Err(AddPartitionError::NoSpace) => break,
1743                };
1744            }
1745            assert!(num <= 40);
1746            manager.commit_transaction(transaction).await.expect("Commit failed");
1747
1748            // Check state before and after a reload.
1749            assert_eq!(manager.header().num_parts, 40);
1750            assert_eq!(manager.partitions().len(), num);
1751
1752            // If the GPT implementation has appropriate barriers/flushes between secondary and
1753            // primary metadata updates, then we will end up in either the old state or the new
1754            // state.  Otherwise, both copies might become corrupt and the GPT would be unreadable.
1755            let manager = Gpt::open(manager.take_client()).await.expect("reload should succeed");
1756            let len = manager.partitions().len();
1757            assert!(len == 0 || len == num);
1758        }
1759    }
1760}