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