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