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