Skip to main content

gpt_component/
gpt.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 crate::config::Config;
6use crate::partition::PartitionBackend;
7use crate::partitions_directory::PartitionsDirectory;
8use anyhow::{Context as _, Error, ensure};
9use block_client::{
10    BlockClient as _, BufferSlice, MutableBufferSlice, ReadOptions, RemoteBlockClient, VmoId,
11    WriteOptions,
12};
13use block_server::async_interface::SessionManager;
14use block_server::{BlockServer, OffsetMap};
15
16use fidl::endpoints::ServerEnd;
17use fidl_fuchsia_storage_block as fblock;
18use fidl_fuchsia_storage_partitions as fpartitions;
19use fs_management::format::constants::{
20    ALL_BENCHMARK_PARTITION_LABELS, ALL_SYSTEM_PARTITION_LABELS,
21};
22use fuchsia_async as fasync;
23use fuchsia_sync::Mutex;
24use futures::stream::TryStreamExt as _;
25use std::collections::BTreeMap;
26use std::num::NonZero;
27use std::sync::atomic::{AtomicBool, Ordering};
28use std::sync::{Arc, Weak};
29
30fn partition_directory_entry_name(index: u32) -> String {
31    format!("part-{:03}", index)
32}
33
34// We use heuristics to decide which partitions to pass through.
35// Partitions which are passed through consume more resources on the underlying block device (e.g. a
36// dedicated per-session thread in some implementations), but have better performance due to not
37// needing to proxy requests through this component.  As such, the idea is that we only pass through
38// "hot" partitions.
39// This list should stay small.
40fn should_passthrough_partition(info: &block_server::PartitionInfo) -> bool {
41    // Partition contains the main filesystem
42    ALL_SYSTEM_PARTITION_LABELS.contains(&info.name.as_str())
43    // Partitions are used for benchmarks which should replicate the performance
44    // of the main filesystem
45    || ALL_BENCHMARK_PARTITION_LABELS.contains(&info.name.as_str())
46    // We always pass through composite partitions, since the mechanism we use for passthrough is to
47    // open sessions with an OffsetMap.  We could locally resolve the offsets, but there's no reason
48    // to implement that right now.
49    || info.start_block_offset.is_none()
50}
51
52fn single_partition_mapping(info: &block_server::PartitionInfo) -> Result<OffsetMap, Error> {
53    Ok(OffsetMap::new(vec![block_server::BlockOffsetMapping {
54        target_block_offset: info.start_block_offset.ok_or(zx::Status::INVALID_ARGS)?,
55        length: info.block_count,
56    }])?)
57}
58
59/// A single partition in a GPT device.
60pub struct GptPartition {
61    gpt: Weak<GptManager>,
62    info: Mutex<block_server::PartitionInfo>,
63    block_client: Arc<RemoteBlockClient>,
64}
65
66fn trace_id(trace_flow_id: Option<NonZero<u64>>) -> u64 {
67    trace_flow_id.map(|v| v.get()).unwrap_or_default()
68}
69
70impl GptPartition {
71    pub fn new(
72        gpt: &Arc<GptManager>,
73        block_client: Arc<RemoteBlockClient>,
74        info: block_server::PartitionInfo,
75    ) -> Arc<Self> {
76        Arc::new(Self { gpt: Arc::downgrade(gpt), info: Mutex::new(info), block_client })
77    }
78
79    pub async fn terminate(&self) {
80        if let Err(error) = self.block_client.close().await {
81            log::warn!(error:?; "Failed to close block client");
82        }
83    }
84
85    pub fn update_info(&self, info: gpt::PartitionInfo) {
86        *self.info.lock() = info.into();
87    }
88
89    pub fn block_size(&self) -> u32 {
90        self.block_client.block_size()
91    }
92
93    pub fn block_count(&self) -> u64 {
94        self.info.lock().block_count
95    }
96
97    /// Attaches the VMO.
98    ///
99    /// # Safety
100    ///
101    /// The caller must guarantee that the VMO is only attached once.  The reason for this is that
102    /// if the far end suddenly disconnects, it is not safe to assume the VMO will not be written to
103    /// in any way: the VMO could be the target of an ongoing DMA transfer.
104    ///
105    /// The caller must also ensure that no references are held during I/O as this would be
106    /// undefined behavior.  The caller may hold pointers, which does not lead to undefined
107    /// behavior; Rust does not make the same assumptions as references for pointers.
108    pub async unsafe fn attach_vmo(&self, vmo: &zx::Vmo) -> Result<VmoId, zx::Status> {
109        // SAFETY: The caller must guarantee that the VMO is only attached once and no references
110        // are held during I/O.
111        unsafe { self.block_client.attach_vmo(vmo) }.await
112    }
113
114    pub async fn detach_vmo(&self, vmoid: VmoId) -> Result<(), zx::Status> {
115        self.block_client.detach_vmo(vmoid).await
116    }
117
118    pub fn open_passthrough_session(
119        &self,
120        session: ServerEnd<fblock::SessionMarker>,
121        offset_map: &OffsetMap,
122    ) {
123        if let Some(gpt) = self.gpt.upgrade() {
124            let mappings: Vec<fblock::BlockOffsetMapping> = offset_map.into();
125            if let Err(error) = gpt.block_proxy.open_session_with_options(session, &mappings[..]) {
126                // Client errors normally come back on `session` but that was already consumed.  The
127                // client will get a PEER_CLOSED without an epitaph.
128                log::warn!(error:?; "Failed to open passthrough session");
129            }
130        } else {
131            if let Err(error) = session.close_with_epitaph(zx::Status::BAD_STATE) {
132                log::warn!(error:?; "Failed to send session epitaph");
133            }
134        }
135    }
136
137    pub fn get_info(&self) -> block_server::DeviceInfo {
138        let mut info = self.info.lock().clone();
139        info.device_flags = self.block_client.block_flags();
140        info.max_transfer_blocks = self.block_client.max_transfer_blocks();
141        block_server::DeviceInfo::Partition(info)
142    }
143
144    pub async fn read(
145        &self,
146        device_block_offset: u64,
147        block_count: u32,
148        vmo_id: &VmoId,
149        vmo_offset: u64, // *bytes* not blocks
150        opts: ReadOptions,
151        trace_flow_id: Option<NonZero<u64>>,
152    ) -> Result<(), zx::Status> {
153        let dev_offset = self
154            .absolute_offset(device_block_offset, block_count)
155            .map(|offset| offset * self.block_size() as u64)?;
156        let buffer = MutableBufferSlice::new_with_vmo_id(
157            vmo_id,
158            vmo_offset,
159            (block_count * self.block_size()) as u64,
160        );
161        self.block_client
162            .read_at_with_opts_traced(buffer, dev_offset, opts, trace_id(trace_flow_id))
163            .await
164    }
165
166    pub async fn write(
167        &self,
168        device_block_offset: u64,
169        block_count: u32,
170        vmo_id: &VmoId,
171        vmo_offset: u64, // *bytes* not blocks
172        opts: WriteOptions,
173        trace_flow_id: Option<NonZero<u64>>,
174    ) -> Result<(), zx::Status> {
175        let dev_offset = self
176            .absolute_offset(device_block_offset, block_count)
177            .map(|offset| offset * self.block_size() as u64)?;
178        let buffer = BufferSlice::new_with_vmo_id(
179            vmo_id,
180            vmo_offset,
181            (block_count * self.block_size()) as u64,
182        );
183        self.block_client
184            .write_at_with_opts_traced(buffer, dev_offset, opts, trace_id(trace_flow_id))
185            .await
186    }
187
188    pub async fn flush(&self, trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
189        self.block_client.flush_traced(trace_id(trace_flow_id)).await
190    }
191
192    pub async fn trim(
193        &self,
194        device_block_offset: u64,
195        block_count: u32,
196        trace_flow_id: Option<NonZero<u64>>,
197    ) -> Result<(), zx::Status> {
198        let dev_offset = self
199            .absolute_offset(device_block_offset, block_count)
200            .map(|offset| offset * self.block_size() as u64)?;
201        let len = block_count as u64 * self.block_size() as u64;
202        let end = dev_offset.checked_add(len).ok_or(zx::Status::OUT_OF_RANGE)?;
203
204        self.block_client.trim_traced(dev_offset..end, trace_id(trace_flow_id)).await
205    }
206
207    // Converts a relative range specified by [offset, offset+len) into an absolute offset in the
208    // GPT device, performing bounds checking within the partition.  Returns ZX_ERR_OUT_OF_RANGE for
209    // an invalid offset/len.
210    fn absolute_offset(&self, mut offset: u64, len: u32) -> Result<u64, zx::Status> {
211        let info = self.info.lock();
212        let Some(start_block) = info.start_block_offset else {
213            // This indicates that a composite partition was not passed through, which is an error
214            // in this library.
215            return Err(zx::Status::BAD_STATE);
216        };
217        offset = offset.checked_add(start_block).ok_or(zx::Status::OUT_OF_RANGE)?;
218        let end = offset.checked_add(len as u64).ok_or(zx::Status::OUT_OF_RANGE)?;
219        if end > start_block + info.block_count {
220            Err(zx::Status::OUT_OF_RANGE)
221        } else {
222            Ok(offset)
223        }
224    }
225}
226
227struct PendingTransaction {
228    transaction: gpt::Transaction,
229    client_koid: zx::Koid,
230    // A list of indexes for partitions which were added in the transaction.  When committing, all
231    // newly created partitions are published.
232    added_partitions: Vec<u32>,
233    // A task which waits for the client end to be closed and clears the pending transaction.
234    _signal_task: fasync::Task<()>,
235}
236
237struct Inner {
238    gpt: gpt::Gpt,
239    partitions: BTreeMap<u32, Arc<BlockServer<SessionManager<PartitionBackend>>>>,
240    // We track these separately so that we do not update them during transaction commit.
241    composite_partitions: BTreeMap<u32, Arc<BlockServer<SessionManager<PartitionBackend>>>>,
242    // Exposes all partitions for discovery by other components.  Should be kept in sync with
243    // `partitions`.
244    partitions_dir: PartitionsDirectory,
245    pending_transaction: Option<PendingTransaction>,
246}
247
248impl Inner {
249    /// Ensures that `transaction` matches our pending transaction.
250    fn ensure_transaction_matches(&self, transaction: &zx::EventPair) -> Result<(), zx::Status> {
251        if let Some(pending) = self.pending_transaction.as_ref() {
252            if transaction.koid()? == pending.client_koid {
253                Ok(())
254            } else {
255                Err(zx::Status::BAD_HANDLE)
256            }
257        } else {
258            Err(zx::Status::BAD_STATE)
259        }
260    }
261
262    fn bind_partition(
263        &mut self,
264        parent: &Arc<GptManager>,
265        index: u32,
266        info: block_server::PartitionInfo,
267        composite_mappings: OffsetMap,
268        composite_indexes: Vec<usize>,
269    ) -> Result<(), Error> {
270        ensure!(
271            composite_indexes.is_empty() == composite_mappings.is_empty(),
272            "Composite partitions must provide mappings"
273        );
274        let passthrough = should_passthrough_partition(&info);
275        let mappings = if passthrough && composite_mappings.is_empty() {
276            // Synthesize a mapping for a non-composite passthrough partition.
277            single_partition_mapping(&info)?
278        } else {
279            // Either this is a composite partition which already has a mapping, or it is a
280            // non-composite partition which is not passed through (in which case this is an empty
281            // mapping).
282            composite_mappings
283        };
284        log::debug!(
285            "GPT part {index}{}{}: {info:?}",
286            if !composite_indexes.is_empty() { " (composite)" } else { "" },
287            if passthrough { " (passthrough)" } else { "" },
288        );
289        let partition = PartitionBackend::new(
290            GptPartition::new(parent, self.gpt.client().clone(), info),
291            mappings,
292        );
293        let block_server = Arc::new(BlockServer::new(parent.block_size, partition));
294        if !composite_indexes.is_empty() {
295            self.partitions_dir.add_composite(
296                &partition_directory_entry_name(index),
297                Arc::downgrade(&block_server),
298                Arc::downgrade(parent),
299                composite_indexes,
300            );
301            self.composite_partitions.insert(index, block_server);
302        } else {
303            self.partitions_dir.add_partition(
304                &partition_directory_entry_name(index),
305                Arc::downgrade(&block_server),
306                Arc::downgrade(parent),
307                index as usize,
308            );
309            self.partitions.insert(index, block_server);
310        }
311        Ok(())
312    }
313
314    fn bind_super_and_userdata_partition(
315        &mut self,
316        parent: &Arc<GptManager>,
317        super_partition: (u32, gpt::PartitionInfo),
318        userdata_partition: (u32, gpt::PartitionInfo),
319    ) -> Result<(), Error> {
320        let extent1 = block_server::BlockOffsetMapping {
321            target_block_offset: super_partition.1.start_block,
322            length: super_partition.1.num_blocks,
323        };
324        let extent2 = block_server::BlockOffsetMapping {
325            target_block_offset: userdata_partition.1.start_block,
326            length: userdata_partition.1.num_blocks,
327        };
328        let mappings =
329            block_server::OffsetMap::new(block_server::coalesce_mappings(vec![extent1, extent2]))?;
330        let info = block_server::PartitionInfo {
331            // TODO(https://fxbug.dev/443980711): This should come from configuration.
332            name: "super_and_userdata".to_string(),
333            type_guid: super_partition.1.type_guid.to_bytes(),
334            instance_guid: super_partition.1.instance_guid.to_bytes(),
335            block_count: mappings.total_blocks(),
336            ..Default::default()
337        };
338        log::trace!(
339            "GPT merged parts {:?} + {:?} -> {info:?}",
340            super_partition.1,
341            userdata_partition.1
342        );
343        self.bind_partition(
344            parent,
345            super_partition.0,
346            info,
347            mappings,
348            vec![super_partition.0 as usize, userdata_partition.0 as usize],
349        )
350    }
351
352    fn bind_all_partitions(&mut self, parent: &Arc<GptManager>) -> Result<(), Error> {
353        self.partitions.clear();
354        self.composite_partitions.clear();
355        self.partitions_dir.clear();
356
357        let mut partitions = self.gpt.partitions().clone();
358        if parent.config.merge_super_and_userdata {
359            // Attempt to merge the first `super` and `userdata` we find.  The rest will be treated
360            // as regular partitions.
361            let super_part = match partitions
362                .iter()
363                .find(|(_, info)| info.label == "super")
364                .map(|(index, _)| *index)
365            {
366                Some(index) => partitions.remove_entry(&index),
367                None => None,
368            };
369            let userdata_part = match partitions
370                .iter()
371                .find(|(_, info)| info.label == "userdata")
372                .map(|(index, _)| *index)
373            {
374                Some(index) => partitions.remove_entry(&index),
375                None => None,
376            };
377            if super_part.is_some() && userdata_part.is_some() {
378                let super_part = super_part.unwrap();
379                let userdata_part = userdata_part.unwrap();
380                self.bind_super_and_userdata_partition(parent, super_part, userdata_part)?;
381            } else if super_part.is_some() || userdata_part.is_some() {
382                log::warn!("Only one of super/userdata found; not merging");
383                let (index, info) = super_part.or(userdata_part).unwrap();
384                self.bind_partition(
385                    parent,
386                    index,
387                    block_server::PartitionInfo::from(&info),
388                    OffsetMap::empty(),
389                    vec![],
390                )?;
391            }
392        }
393        for (index, info) in partitions {
394            self.bind_partition(
395                parent,
396                index,
397                block_server::PartitionInfo::from(&info),
398                OffsetMap::empty(),
399                vec![],
400            )?;
401        }
402        Ok(())
403    }
404
405    fn add_partition(&mut self, info: gpt::PartitionInfo) -> Result<usize, gpt::AddPartitionError> {
406        let pending = self.pending_transaction.as_mut().unwrap();
407        let idx = self.gpt.add_partition(&mut pending.transaction, info)?;
408        pending.added_partitions.push(idx as u32);
409        Ok(idx)
410    }
411}
412
413/// Runs a GPT device.
414pub struct GptManager {
415    config: Config,
416    block_proxy: fblock::BlockProxy,
417    block_size: u32,
418    block_count: u64,
419    inner: futures::lock::Mutex<Inner>,
420    shutdown: AtomicBool,
421}
422
423impl std::fmt::Debug for GptManager {
424    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
425        f.debug_struct("GptManager")
426            .field("block_size", &self.block_size)
427            .field("block_count", &self.block_count)
428            .finish()
429    }
430}
431
432impl GptManager {
433    pub async fn new(
434        block_proxy: fblock::BlockProxy,
435        partitions_dir: Arc<vfs::directory::immutable::Simple>,
436    ) -> Result<Arc<Self>, Error> {
437        Self::new_with_config(block_proxy, partitions_dir, Config::default()).await
438    }
439
440    pub async fn new_with_config(
441        block_proxy: fblock::BlockProxy,
442        partitions_dir: Arc<vfs::directory::immutable::Simple>,
443        config: Config,
444    ) -> Result<Arc<Self>, Error> {
445        log::info!("Binding to GPT");
446        let client = Arc::new(RemoteBlockClient::new(block_proxy.clone()).await?);
447        let block_size = client.block_size();
448        let block_count = client.block_count();
449        let gpt = gpt::Gpt::open(client).await.context("Failed to load GPT")?;
450
451        let this = Arc::new(Self {
452            config,
453            block_proxy,
454            block_size,
455            block_count,
456            inner: futures::lock::Mutex::new(Inner {
457                gpt,
458                partitions: BTreeMap::new(),
459                composite_partitions: BTreeMap::new(),
460                partitions_dir: PartitionsDirectory::new(partitions_dir),
461                pending_transaction: None,
462            }),
463            shutdown: AtomicBool::new(false),
464        });
465        this.inner.lock().await.bind_all_partitions(&this)?;
466        log::info!("Starting all partitions OK!");
467        Ok(this)
468    }
469
470    pub fn block_size(&self) -> u32 {
471        self.block_size
472    }
473
474    pub fn block_count(&self) -> u64 {
475        self.block_count
476    }
477
478    pub async fn create_transaction(self: &Arc<Self>) -> Result<zx::EventPair, zx::Status> {
479        let mut inner = self.inner.lock().await;
480        if inner.pending_transaction.is_some() {
481            return Err(zx::Status::ALREADY_EXISTS);
482        }
483        let transaction = inner.gpt.create_transaction().unwrap();
484        let (client_end, server_end) = zx::EventPair::create();
485        let client_koid = client_end.koid()?;
486        let signal_waiter = fasync::OnSignals::new(server_end, zx::Signals::EVENTPAIR_PEER_CLOSED);
487        let this = self.clone();
488        let task = fasync::Task::spawn(async move {
489            let _ = signal_waiter.await;
490            let mut inner = this.inner.lock().await;
491            if inner.pending_transaction.as_ref().map_or(false, |t| t.client_koid == client_koid) {
492                inner.pending_transaction = None;
493            }
494        });
495        inner.pending_transaction = Some(PendingTransaction {
496            transaction,
497            client_koid,
498            added_partitions: vec![],
499            _signal_task: task,
500        });
501        Ok(client_end)
502    }
503
504    pub async fn commit_transaction(
505        self: &Arc<Self>,
506        transaction: zx::EventPair,
507    ) -> Result<(), zx::Status> {
508        let mut inner = self.inner.lock().await;
509        inner.ensure_transaction_matches(&transaction)?;
510        let pending = std::mem::take(&mut inner.pending_transaction).unwrap();
511        let partitions = pending.transaction.partitions.clone();
512        if let Err(error) = inner.gpt.commit_transaction(pending.transaction).await {
513            log::warn!(error:?; "Failed to commit transaction");
514            return Err(zx::Status::IO);
515        }
516        // Everything after this point should be infallible.
517        for (info, idx) in partitions
518            .iter()
519            .zip(0u32..)
520            .filter(|(info, idx)| !info.is_nil() && !pending.added_partitions.contains(idx))
521        {
522            // Some physical partitions are not tracked in `inner.partitions` (e.g. when we use an
523            // composite partition to combine two physical partitions).  In this case, we still need
524            // to propagate the info in the underlying transaction, but there's no need to update
525            // the in-memory info.
526            // Note that composite partitions can't be changed by transactions anyways, so the info
527            // we propagate should be exactly what it was when we created the transaction.
528            if let Some(part) = inner.partitions.get(&idx) {
529                part.session_manager().interface().update_info(info.clone());
530            }
531        }
532        for idx in pending.added_partitions {
533            if let Some(gpt_info) = inner.gpt.partitions().get(&idx).cloned() {
534                let partition_info = block_server::PartitionInfo::from(&gpt_info);
535                if let Err(error) =
536                    inner.bind_partition(self, idx, partition_info, OffsetMap::empty(), vec![])
537                {
538                    log::error!(error:?; "Failed to bind partition");
539                }
540            }
541        }
542        Ok(())
543    }
544
545    pub async fn add_partition(
546        &self,
547        request: fpartitions::PartitionsManagerAddPartitionRequest,
548    ) -> Result<(), zx::Status> {
549        let mut inner = self.inner.lock().await;
550        inner.ensure_transaction_matches(
551            request.transaction.as_ref().ok_or(zx::Status::BAD_HANDLE)?,
552        )?;
553        let info = gpt::PartitionInfo {
554            label: request.name.ok_or(zx::Status::INVALID_ARGS)?,
555            type_guid: request
556                .type_guid
557                .map(|value| gpt::Guid::from_bytes(value.value))
558                .ok_or(zx::Status::INVALID_ARGS)?,
559            instance_guid: request
560                .instance_guid
561                .map(|value| gpt::Guid::from_bytes(value.value))
562                .unwrap_or_else(|| gpt::Guid::generate()),
563            start_block: 0,
564            num_blocks: request.num_blocks.ok_or(zx::Status::INVALID_ARGS)?,
565            flags: request.flags.unwrap_or_default(),
566        };
567        let idx = inner.add_partition(info)?;
568        let partition =
569            inner.pending_transaction.as_ref().unwrap().transaction.partitions.get(idx).unwrap();
570        log::info!(
571            "Allocated partition {:?} at {:?}",
572            partition.label,
573            partition.start_block..partition.start_block + partition.num_blocks
574        );
575        Ok(())
576    }
577
578    pub async fn handle_partitions_requests(
579        &self,
580        gpt_index: usize,
581        mut requests: fpartitions::PartitionRequestStream,
582    ) -> Result<(), zx::Status> {
583        while let Some(request) = requests.try_next().await.unwrap() {
584            match request {
585                fpartitions::PartitionRequest::UpdateMetadata { payload, responder } => {
586                    responder
587                        .send(
588                            self.update_partition_metadata(gpt_index, payload)
589                                .await
590                                .map_err(|status| status.into_raw()),
591                        )
592                        .unwrap_or_else(
593                            |error| log::error!(error:?; "Failed to send UpdateMetadata response"),
594                        );
595                }
596            }
597        }
598        Ok(())
599    }
600
601    async fn update_partition_metadata(
602        &self,
603        gpt_index: usize,
604        request: fpartitions::PartitionUpdateMetadataRequest,
605    ) -> Result<(), zx::Status> {
606        let mut inner = self.inner.lock().await;
607        inner.ensure_transaction_matches(
608            request.transaction.as_ref().ok_or(zx::Status::BAD_HANDLE)?,
609        )?;
610
611        let transaction = &mut inner.pending_transaction.as_mut().unwrap().transaction;
612        let entry = transaction.partitions.get_mut(gpt_index).ok_or(zx::Status::BAD_STATE)?;
613        if let Some(type_guid) = request.type_guid.as_ref().cloned() {
614            entry.type_guid = gpt::Guid::from_bytes(type_guid.value);
615        }
616        if let Some(flags) = request.flags.as_ref() {
617            entry.flags = *flags;
618        }
619        Ok(())
620    }
621
622    pub async fn handle_composite_partitions_requests(
623        &self,
624        gpt_indexes: Vec<usize>,
625        mut requests: fpartitions::OverlayPartitionRequestStream,
626    ) -> Result<(), zx::Status> {
627        while let Some(request) = requests.try_next().await.unwrap() {
628            match request {
629                fpartitions::OverlayPartitionRequest::GetPartitions { responder } => {
630                    match self.get_composite_partition_info(&gpt_indexes[..]).await {
631                        Ok(partitions) => responder.send(Ok(&partitions[..])),
632                        Err(status) => responder.send(Err(status.into_raw())),
633                    }
634                    .unwrap_or_else(
635                        |error| log::error!(error:?; "Failed to send GetPartitions response"),
636                    );
637                }
638            }
639        }
640        Ok(())
641    }
642
643    async fn get_composite_partition_info(
644        &self,
645        gpt_indexes: &[usize],
646    ) -> Result<Vec<fpartitions::PartitionInfo>, zx::Status> {
647        fn convert_partition_info(info: &gpt::PartitionInfo) -> fpartitions::PartitionInfo {
648            fpartitions::PartitionInfo {
649                name: Some(info.label.to_string()),
650                type_guid: Some(fblock::Guid { value: info.type_guid.to_bytes() }),
651                instance_guid: Some(fblock::Guid { value: info.instance_guid.to_bytes() }),
652                start_block_offset: Some(info.start_block),
653                num_blocks: Some(info.num_blocks),
654                flags: Some(info.flags),
655                ..Default::default()
656            }
657        }
658
659        let inner = self.inner.lock().await;
660        let mut partitions = vec![];
661        for index in gpt_indexes {
662            let index: u32 = *index as u32;
663            partitions.push(
664                inner
665                    .gpt
666                    .partitions()
667                    .get(&index)
668                    .map(convert_partition_info)
669                    .ok_or(zx::Status::BAD_STATE)?,
670            );
671        }
672        Ok(partitions)
673    }
674
675    pub async fn reset_partition_table(
676        self: &Arc<Self>,
677        partitions: Vec<gpt::PartitionInfo>,
678    ) -> Result<(), zx::Status> {
679        let mut inner = self.inner.lock().await;
680        if inner.pending_transaction.is_some() {
681            return Err(zx::Status::BAD_STATE);
682        }
683
684        log::info!("Resetting gpt.  Expect data loss!!!");
685        let mut transaction = inner.gpt.create_transaction().unwrap();
686        transaction.partitions = partitions;
687        inner.gpt.commit_transaction(transaction).await?;
688
689        if let Err(error) = inner.bind_all_partitions(&self) {
690            log::error!(error:?; "Failed to rebind partitions");
691            return Err(zx::Status::BAD_STATE);
692        }
693        log::info!("Rebinding partitions OK!");
694        Ok(())
695    }
696
697    pub async fn shutdown(self: Arc<Self>) {
698        log::info!("Shutting down gpt");
699        let mut inner = self.inner.lock().await;
700        inner.partitions_dir.clear();
701        inner.partitions.clear();
702        inner.composite_partitions.clear();
703        self.shutdown.store(true, Ordering::Relaxed);
704        log::info!("Shutting down gpt OK");
705    }
706}
707
708impl Drop for GptManager {
709    fn drop(&mut self) {
710        assert!(self.shutdown.load(Ordering::Relaxed), "Did you forget to shutdown?");
711    }
712}
713
714#[cfg(test)]
715mod tests {
716    use super::GptManager;
717    use block_client::{
718        BlockClient as _, BlockDeviceFlag, BufferSlice, MutableBufferSlice, RemoteBlockClient,
719        WriteFlags,
720    };
721    use block_server::{BlockInfo, DeviceInfo, WriteOptions};
722    use fidl_fuchsia_io as fio;
723    use fidl_fuchsia_storage_block as fblock;
724    use fidl_fuchsia_storage_partitions as fpartitions;
725    use fuchsia_async as fasync;
726    use fuchsia_component::client::connect_to_named_protocol_at_dir_root;
727    use gpt::{Gpt, Guid, PartitionInfo};
728    use std::num::NonZero;
729    use std::sync::Arc;
730    use std::sync::atomic::{AtomicBool, Ordering};
731    use test_vmo_backed_block_server::{
732        InitialContents, Observer, VmoBackedServer, VmoBackedServerOptions, WriteAction,
733    };
734
735    async fn setup(
736        block_size: u32,
737        block_count: u64,
738        partitions: Vec<PartitionInfo>,
739    ) -> (Arc<VmoBackedServer>, Arc<vfs::directory::immutable::Simple>) {
740        setup_with_options(
741            VmoBackedServerOptions {
742                initial_contents: InitialContents::FromCapacity(block_count),
743                block_size,
744                ..Default::default()
745            },
746            partitions,
747        )
748        .await
749    }
750
751    async fn setup_with_options(
752        opts: VmoBackedServerOptions<'_>,
753        partitions: Vec<PartitionInfo>,
754    ) -> (Arc<VmoBackedServer>, Arc<vfs::directory::immutable::Simple>) {
755        let server = Arc::new(opts.build().unwrap());
756        {
757            let (block_client, block_server) =
758                fidl::endpoints::create_proxy::<fblock::BlockMarker>();
759            let volume_stream = fidl::endpoints::ServerEnd::<fblock::BlockMarker>::from(
760                block_server.into_channel(),
761            )
762            .into_stream();
763            let server_clone = server.clone();
764            let _task = fasync::Task::spawn(async move { server_clone.serve(volume_stream).await });
765            let client = Arc::new(RemoteBlockClient::new(block_client).await.unwrap());
766            Gpt::format(client, partitions).await.unwrap();
767        }
768        (server, vfs::directory::immutable::simple())
769    }
770
771    #[fuchsia::test]
772    async fn load_unformatted_gpt() {
773        let server =
774            Arc::new(VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer"));
775
776        GptManager::new(server.connect(), vfs::directory::immutable::simple())
777            .await
778            .expect_err("load should fail");
779    }
780
781    #[fuchsia::test]
782    async fn load_formatted_empty_gpt() {
783        let (block_device, partitions_dir) = setup(512, 8, vec![]).await;
784
785        let runner = GptManager::new(block_device.connect(), partitions_dir)
786            .await
787            .expect("load should succeed");
788        runner.shutdown().await;
789    }
790
791    #[fuchsia::test]
792    async fn load_formatted_gpt_with_one_partition() {
793        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
794        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
795        const PART_NAME: &str = "part";
796
797        let (block_device, partitions_dir) = setup(
798            512,
799            8,
800            vec![PartitionInfo {
801                label: PART_NAME.to_string(),
802                type_guid: Guid::from_bytes(PART_TYPE_GUID),
803                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
804                start_block: 4,
805                num_blocks: 1,
806                flags: 0,
807            }],
808        )
809        .await;
810
811        let partitions_dir_clone = partitions_dir.clone();
812        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
813            .await
814            .expect("load should succeed");
815        partitions_dir.get_entry("part-000").expect("No entry found");
816        partitions_dir.get_entry("part-001").map(|_| ()).expect_err("Extra entry found");
817        runner.shutdown().await;
818    }
819
820    #[fuchsia::test]
821    async fn load_formatted_gpt_with_two_partitions() {
822        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
823        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
824        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
825        const PART_1_NAME: &str = "part1";
826        const PART_2_NAME: &str = "part2";
827
828        let (block_device, partitions_dir) = setup(
829            512,
830            8,
831            vec![
832                PartitionInfo {
833                    label: PART_1_NAME.to_string(),
834                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
835                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
836                    start_block: 4,
837                    num_blocks: 1,
838                    flags: 0,
839                },
840                PartitionInfo {
841                    label: PART_2_NAME.to_string(),
842                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
843                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
844                    start_block: 5,
845                    num_blocks: 1,
846                    flags: 0,
847                },
848            ],
849        )
850        .await;
851
852        let partitions_dir_clone = partitions_dir.clone();
853        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
854            .await
855            .expect("load should succeed");
856        partitions_dir.get_entry("part-000").expect("No entry found");
857        partitions_dir.get_entry("part-001").expect("No entry found");
858        partitions_dir.get_entry("part-002").map(|_| ()).expect_err("Extra entry found");
859        runner.shutdown().await;
860    }
861
862    #[fuchsia::test]
863    async fn partition_io() {
864        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
865        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
866        const PART_NAME: &str = "part";
867
868        let (block_device, partitions_dir) = setup(
869            512,
870            8,
871            vec![PartitionInfo {
872                label: PART_NAME.to_string(),
873                type_guid: Guid::from_bytes(PART_TYPE_GUID),
874                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
875                start_block: 4,
876                num_blocks: 2,
877                flags: 0,
878            }],
879        )
880        .await;
881
882        let partitions_dir_clone = partitions_dir.clone();
883        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
884            .await
885            .expect("load should succeed");
886
887        let proxy = vfs::serve_directory(
888            partitions_dir.clone(),
889            vfs::path::Path::validate_and_split("part-000").unwrap(),
890            vfs::execution_scope::ExecutionScope::new(),
891            fio::PERM_READABLE,
892        );
893        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
894            .expect("Failed to open block service");
895        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
896
897        assert_eq!(client.block_count(), 2);
898        assert_eq!(client.block_size(), 512);
899
900        let buf = vec![0xabu8; 512];
901        client.write_at(BufferSlice::Memory(&buf[..]), 0).await.expect("write_at failed");
902        client
903            .write_at(BufferSlice::Memory(&buf[..]), 1024)
904            .await
905            .expect_err("write_at should fail when writing past partition end");
906        let mut buf2 = vec![0u8; 512];
907        client.read_at(MutableBufferSlice::Memory(&mut buf2[..]), 0).await.expect("read_at failed");
908        assert_eq!(buf, buf2);
909        client
910            .read_at(MutableBufferSlice::Memory(&mut buf2[..]), 1024)
911            .await
912            .expect_err("read_at should fail when reading past partition end");
913        client.trim(512..1024).await.expect("trim failed");
914        client.trim(1..512).await.expect_err("trim with invalid range should fail");
915        client.trim(1024..1536).await.expect_err("trim past end of partition should fail");
916        runner.shutdown().await;
917
918        // Ensure writes persisted to the partition.
919        let mut buf = vec![0u8; 512];
920        let client =
921            RemoteBlockClient::new(block_device.connect::<fblock::BlockProxy>()).await.unwrap();
922        client.read_at(MutableBufferSlice::Memory(&mut buf[..]), 2048).await.unwrap();
923        assert_eq!(&buf[..], &[0xabu8; 512]);
924    }
925
926    #[fuchsia::test]
927    async fn load_formatted_gpt_with_invalid_primary_header() {
928        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
929        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
930        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
931        const PART_1_NAME: &str = "part1";
932        const PART_2_NAME: &str = "part2";
933
934        let (block_device, partitions_dir) = setup(
935            512,
936            8,
937            vec![
938                PartitionInfo {
939                    label: PART_1_NAME.to_string(),
940                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
941                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
942                    start_block: 4,
943                    num_blocks: 1,
944                    flags: 0,
945                },
946                PartitionInfo {
947                    label: PART_2_NAME.to_string(),
948                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
949                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
950                    start_block: 5,
951                    num_blocks: 1,
952                    flags: 0,
953                },
954            ],
955        )
956        .await;
957        {
958            let (client, stream) =
959                fidl::endpoints::create_proxy_and_stream::<fblock::BlockMarker>();
960            let server = block_device.clone();
961            let _task = fasync::Task::spawn(async move { server.serve(stream).await });
962            let client = RemoteBlockClient::new(client).await.unwrap();
963            client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
964        }
965
966        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
967            .await
968            .expect("load should succeed");
969        partitions_dir.get_entry("part-000").expect("No entry found");
970        partitions_dir.get_entry("part-001").expect("No entry found");
971        runner.shutdown().await;
972    }
973
974    #[fuchsia::test]
975    async fn load_formatted_gpt_with_invalid_primary_partition_table() {
976        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
977        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
978        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
979        const PART_1_NAME: &str = "part1";
980        const PART_2_NAME: &str = "part2";
981
982        let (block_device, partitions_dir) = setup(
983            512,
984            8,
985            vec![
986                PartitionInfo {
987                    label: PART_1_NAME.to_string(),
988                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
989                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
990                    start_block: 4,
991                    num_blocks: 1,
992                    flags: 0,
993                },
994                PartitionInfo {
995                    label: PART_2_NAME.to_string(),
996                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
997                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
998                    start_block: 5,
999                    num_blocks: 1,
1000                    flags: 0,
1001                },
1002            ],
1003        )
1004        .await;
1005        {
1006            let (client, stream) =
1007                fidl::endpoints::create_proxy_and_stream::<fblock::BlockMarker>();
1008            let server = block_device.clone();
1009            let _task = fasync::Task::spawn(async move { server.serve(stream).await });
1010            let client = RemoteBlockClient::new(client).await.unwrap();
1011            client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
1012        }
1013
1014        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1015            .await
1016            .expect("load should succeed");
1017        partitions_dir.get_entry("part-000").expect("No entry found");
1018        partitions_dir.get_entry("part-001").expect("No entry found");
1019        runner.shutdown().await;
1020    }
1021
1022    #[fuchsia::test]
1023    async fn force_access_passed_through() {
1024        const BLOCK_SIZE: u32 = 512;
1025        const BLOCK_COUNT: u64 = 1024;
1026
1027        struct ForceAccessObserver(Arc<AtomicBool>);
1028
1029        impl Observer for ForceAccessObserver {
1030            fn write(
1031                &self,
1032                _device_block_offset: u64,
1033                _block_count: u32,
1034                _vmo: &Arc<zx::Vmo>,
1035                _vmo_offset: u64,
1036                opts: WriteOptions,
1037            ) -> WriteAction {
1038                assert_eq!(
1039                    opts.flags.contains(WriteFlags::FORCE_ACCESS),
1040                    self.0.load(Ordering::Relaxed)
1041                );
1042                WriteAction::Write
1043            }
1044        }
1045
1046        let expect_force_access = Arc::new(AtomicBool::new(false));
1047        let (server, partitions_dir) = setup_with_options(
1048            VmoBackedServerOptions {
1049                initial_contents: InitialContents::FromCapacity(BLOCK_COUNT),
1050                block_size: BLOCK_SIZE,
1051                observer: Some(Box::new(ForceAccessObserver(expect_force_access.clone()))),
1052                info: DeviceInfo::Block(BlockInfo {
1053                    device_flags: fblock::DeviceFlag::FUA_SUPPORT,
1054                    ..Default::default()
1055                }),
1056                ..Default::default()
1057            },
1058            vec![PartitionInfo {
1059                label: "foo".to_string(),
1060                type_guid: Guid::from_bytes([1; 16]),
1061                instance_guid: Guid::from_bytes([2; 16]),
1062                start_block: 4,
1063                num_blocks: 1,
1064                flags: 0,
1065            }],
1066        )
1067        .await;
1068
1069        let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
1070
1071        let proxy = vfs::serve_directory(
1072            partitions_dir.clone(),
1073            vfs::path::Path::validate_and_split("part-000").unwrap(),
1074            vfs::execution_scope::ExecutionScope::new(),
1075            fio::PERM_READABLE,
1076        );
1077        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1078            .expect("Failed to open block service");
1079        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1080
1081        let buffer = vec![0; BLOCK_SIZE as usize];
1082        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1083
1084        expect_force_access.store(true, Ordering::Relaxed);
1085
1086        client
1087            .write_at_with_opts(
1088                BufferSlice::Memory(&buffer),
1089                0,
1090                WriteOptions { flags: WriteFlags::FORCE_ACCESS, ..Default::default() },
1091            )
1092            .await
1093            .unwrap();
1094
1095        manager.shutdown().await;
1096    }
1097
1098    #[fuchsia::test]
1099    async fn barrier_passed_through() {
1100        const BLOCK_SIZE: u32 = 512;
1101        const BLOCK_COUNT: u64 = 1024;
1102
1103        struct BarrierObserver(Arc<AtomicBool>);
1104
1105        impl Observer for BarrierObserver {
1106            fn write(
1107                &self,
1108                _device_block_offset: u64,
1109                _block_count: u32,
1110                _vmo: &Arc<zx::Vmo>,
1111                _vmo_offset: u64,
1112                opts: WriteOptions,
1113            ) -> WriteAction {
1114                assert_eq!(
1115                    opts.flags.contains(WriteFlags::PRE_BARRIER),
1116                    self.0.load(Ordering::Relaxed)
1117                );
1118                WriteAction::Write
1119            }
1120        }
1121
1122        let expect_barrier = Arc::new(AtomicBool::new(false));
1123        let (server, partitions_dir) = setup_with_options(
1124            VmoBackedServerOptions {
1125                initial_contents: InitialContents::FromCapacity(BLOCK_COUNT),
1126                block_size: BLOCK_SIZE,
1127                observer: Some(Box::new(BarrierObserver(expect_barrier.clone()))),
1128                info: DeviceInfo::Block(BlockInfo {
1129                    device_flags: fblock::DeviceFlag::BARRIER_SUPPORT,
1130                    ..Default::default()
1131                }),
1132                ..Default::default()
1133            },
1134            vec![PartitionInfo {
1135                label: "foo".to_string(),
1136                type_guid: Guid::from_bytes([1; 16]),
1137                instance_guid: Guid::from_bytes([2; 16]),
1138                start_block: 4,
1139                num_blocks: 1,
1140                flags: 0,
1141            }],
1142        )
1143        .await;
1144
1145        let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
1146
1147        let proxy = vfs::serve_directory(
1148            partitions_dir.clone(),
1149            vfs::path::Path::validate_and_split("part-000").unwrap(),
1150            vfs::execution_scope::ExecutionScope::new(),
1151            fio::PERM_READABLE,
1152        );
1153        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1154            .expect("Failed to open block service");
1155        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1156
1157        let buffer = vec![0; BLOCK_SIZE as usize];
1158        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1159
1160        expect_barrier.store(true, Ordering::Relaxed);
1161        client.barrier();
1162        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1163
1164        manager.shutdown().await;
1165    }
1166
1167    #[fuchsia::test]
1168    async fn commit_transaction() {
1169        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1170        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1171        const PART_1_NAME: &str = "part";
1172        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1173        const PART_2_NAME: &str = "part2";
1174
1175        let (block_device, partitions_dir) = setup(
1176            512,
1177            16,
1178            vec![
1179                PartitionInfo {
1180                    label: PART_1_NAME.to_string(),
1181                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1182                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1183                    start_block: 4,
1184                    num_blocks: 1,
1185                    flags: 0,
1186                },
1187                PartitionInfo {
1188                    label: PART_2_NAME.to_string(),
1189                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1190                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1191                    start_block: 5,
1192                    num_blocks: 1,
1193                    flags: 0,
1194                },
1195            ],
1196        )
1197        .await;
1198        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1199            .await
1200            .expect("load should succeed");
1201
1202        let part_0_dir = vfs::serve_directory(
1203            partitions_dir.clone(),
1204            vfs::Path::validate_and_split("part-000").unwrap(),
1205            vfs::execution_scope::ExecutionScope::new(),
1206            fio::PERM_READABLE,
1207        );
1208        let part_1_dir = vfs::serve_directory(
1209            partitions_dir.clone(),
1210            vfs::Path::validate_and_split("part-001").unwrap(),
1211            vfs::execution_scope::ExecutionScope::new(),
1212            fio::PERM_READABLE,
1213        );
1214        let part_0_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1215            &part_0_dir,
1216            "partition",
1217        )
1218        .expect("Failed to open Partition service");
1219        let part_1_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1220            &part_1_dir,
1221            "partition",
1222        )
1223        .expect("Failed to open Partition service");
1224
1225        let transaction = runner.create_transaction().await.expect("Failed to create transaction");
1226        part_0_proxy
1227            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1228                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1229                type_guid: Some(fblock::Guid { value: [0xffu8; 16] }),
1230                ..Default::default()
1231            })
1232            .await
1233            .expect("FIDL error")
1234            .expect("Failed to update_metadata");
1235        part_1_proxy
1236            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1237                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1238                flags: Some(1234),
1239                ..Default::default()
1240            })
1241            .await
1242            .expect("FIDL error")
1243            .expect("Failed to update_metadata");
1244        runner.commit_transaction(transaction).await.expect("Failed to commit transaction");
1245
1246        // Ensure the changes have propagated to the correct partitions.
1247        let part_0_block =
1248            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
1249                .expect("Failed to open Volume service");
1250        let (status, guid) = part_0_block.get_type_guid().await.expect("FIDL error");
1251        assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
1252        assert_eq!(guid.unwrap().value, [0xffu8; 16]);
1253        let part_1_block =
1254            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_1_dir, "volume")
1255                .expect("Failed to open Volume service");
1256        let metadata =
1257            part_1_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1258        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1259        assert_eq!(metadata.flags, Some(1234));
1260
1261        runner.shutdown().await;
1262    }
1263
1264    #[fuchsia::test]
1265    async fn commit_transaction_with_io_error() {
1266        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1267        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1268        const PART_1_NAME: &str = "part";
1269        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1270        const PART_2_NAME: &str = "part2";
1271
1272        #[derive(Clone)]
1273        struct TransactionObserver(Arc<AtomicBool>);
1274        impl Observer for TransactionObserver {
1275            fn write(
1276                &self,
1277                _device_block_offset: u64,
1278                _block_count: u32,
1279                _vmo: &Arc<zx::Vmo>,
1280                _vmo_offset: u64,
1281                _opts: WriteOptions,
1282            ) -> WriteAction {
1283                if self.0.load(Ordering::Relaxed) { WriteAction::Fail } else { WriteAction::Write }
1284            }
1285        }
1286        let observer = TransactionObserver(Arc::new(AtomicBool::new(false)));
1287        let (block_device, partitions_dir) = setup_with_options(
1288            VmoBackedServerOptions {
1289                initial_contents: InitialContents::FromCapacity(16),
1290                block_size: 512,
1291                observer: Some(Box::new(observer.clone())),
1292                ..Default::default()
1293            },
1294            vec![
1295                PartitionInfo {
1296                    label: PART_1_NAME.to_string(),
1297                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1298                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1299                    start_block: 4,
1300                    num_blocks: 1,
1301                    flags: 0,
1302                },
1303                PartitionInfo {
1304                    label: PART_2_NAME.to_string(),
1305                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1306                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1307                    start_block: 5,
1308                    num_blocks: 1,
1309                    flags: 0,
1310                },
1311            ],
1312        )
1313        .await;
1314        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1315            .await
1316            .expect("load should succeed");
1317
1318        let part_0_dir = vfs::serve_directory(
1319            partitions_dir.clone(),
1320            vfs::Path::validate_and_split("part-000").unwrap(),
1321            vfs::execution_scope::ExecutionScope::new(),
1322            fio::PERM_READABLE,
1323        );
1324        let part_1_dir = vfs::serve_directory(
1325            partitions_dir.clone(),
1326            vfs::Path::validate_and_split("part-001").unwrap(),
1327            vfs::execution_scope::ExecutionScope::new(),
1328            fio::PERM_READABLE,
1329        );
1330        let part_0_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1331            &part_0_dir,
1332            "partition",
1333        )
1334        .expect("Failed to open Partition service");
1335        let part_1_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1336            &part_1_dir,
1337            "partition",
1338        )
1339        .expect("Failed to open Partition service");
1340
1341        let transaction = runner.create_transaction().await.expect("Failed to create transaction");
1342        part_0_proxy
1343            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1344                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1345                type_guid: Some(fblock::Guid { value: [0xffu8; 16] }),
1346                ..Default::default()
1347            })
1348            .await
1349            .expect("FIDL error")
1350            .expect("Failed to update_metadata");
1351        part_1_proxy
1352            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1353                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1354                flags: Some(1234),
1355                ..Default::default()
1356            })
1357            .await
1358            .expect("FIDL error")
1359            .expect("Failed to update_metadata");
1360
1361        observer.0.store(true, Ordering::Relaxed); // Fail the next write
1362        runner.commit_transaction(transaction).await.expect_err("Commit transaction should fail");
1363
1364        // Ensure the changes did not get applied.
1365        let part_0_block =
1366            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
1367                .expect("Failed to open Volume service");
1368        let (status, guid) = part_0_block.get_type_guid().await.expect("FIDL error");
1369        assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
1370        assert_eq!(guid.unwrap().value, [2u8; 16]);
1371        let part_1_block =
1372            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_1_dir, "volume")
1373                .expect("Failed to open Volume service");
1374        let metadata =
1375            part_1_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1376        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1377        assert_eq!(metadata.flags, Some(0));
1378
1379        runner.shutdown().await;
1380    }
1381
1382    #[fuchsia::test]
1383    async fn reset_partition_tables() {
1384        // The test will reset the tables from ["part", "part2"] to
1385        // ["part3", <empty>, "part4", <125 empty entries>].
1386        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1387        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1388        const PART_1_NAME: &str = "part";
1389        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1390        const PART_2_NAME: &str = "part2";
1391        const PART_3_NAME: &str = "part3";
1392        const PART_4_NAME: &str = "part4";
1393
1394        let (block_device, partitions_dir) = setup(
1395            512,
1396            1048576 / 512,
1397            vec![
1398                PartitionInfo {
1399                    label: PART_1_NAME.to_string(),
1400                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1401                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1402                    start_block: 4,
1403                    num_blocks: 1,
1404                    flags: 0,
1405                },
1406                PartitionInfo {
1407                    label: PART_2_NAME.to_string(),
1408                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1409                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1410                    start_block: 5,
1411                    num_blocks: 1,
1412                    flags: 0,
1413                },
1414            ],
1415        )
1416        .await;
1417        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1418            .await
1419            .expect("load should succeed");
1420        let nil_entry = PartitionInfo {
1421            label: "".to_string(),
1422            type_guid: Guid::from_bytes([0u8; 16]),
1423            instance_guid: Guid::from_bytes([0u8; 16]),
1424            start_block: 0,
1425            num_blocks: 0,
1426            flags: 0,
1427        };
1428        let mut new_partitions = vec![nil_entry; 128];
1429        new_partitions[0] = PartitionInfo {
1430            label: PART_3_NAME.to_string(),
1431            type_guid: Guid::from_bytes(PART_TYPE_GUID),
1432            instance_guid: Guid::from_bytes([1u8; 16]),
1433            start_block: 64,
1434            num_blocks: 2,
1435            flags: 0,
1436        };
1437        new_partitions[2] = PartitionInfo {
1438            label: PART_4_NAME.to_string(),
1439            type_guid: Guid::from_bytes(PART_TYPE_GUID),
1440            instance_guid: Guid::from_bytes([2u8; 16]),
1441            start_block: 66,
1442            num_blocks: 4,
1443            flags: 0,
1444        };
1445        runner.reset_partition_table(new_partitions).await.expect("reset_partition_table failed");
1446        partitions_dir.get_entry("part-000").expect("No entry found");
1447        partitions_dir.get_entry("part-001").map(|_| ()).expect_err("Extra entry found");
1448        partitions_dir.get_entry("part-002").expect("No entry found");
1449
1450        let proxy = vfs::serve_directory(
1451            partitions_dir.clone(),
1452            vfs::path::Path::validate_and_split("part-000").unwrap(),
1453            vfs::execution_scope::ExecutionScope::new(),
1454            fio::PERM_READABLE,
1455        );
1456        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1457            .expect("Failed to open block service");
1458        let (status, name) = block.get_name().await.expect("FIDL error");
1459        assert_eq!(zx::Status::from_raw(status), zx::Status::OK);
1460        assert_eq!(name.unwrap(), PART_3_NAME);
1461
1462        runner.shutdown().await;
1463    }
1464
1465    #[fuchsia::test]
1466    async fn reset_partition_tables_fails_if_too_many_partitions() {
1467        let (block_device, partitions_dir) = setup(512, 8, vec![]).await;
1468        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1469            .await
1470            .expect("load should succeed");
1471        let nil_entry = PartitionInfo {
1472            label: "".to_string(),
1473            type_guid: Guid::from_bytes([0u8; 16]),
1474            instance_guid: Guid::from_bytes([0u8; 16]),
1475            start_block: 0,
1476            num_blocks: 0,
1477            flags: 0,
1478        };
1479        let new_partitions = vec![nil_entry; 128];
1480        runner
1481            .reset_partition_table(new_partitions)
1482            .await
1483            .expect_err("reset_partition_table should fail");
1484
1485        runner.shutdown().await;
1486    }
1487
1488    #[fuchsia::test]
1489    async fn reset_partition_tables_fails_if_too_large_partitions() {
1490        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1491        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1492            .await
1493            .expect("load should succeed");
1494        let new_partitions = vec![
1495            PartitionInfo {
1496                label: "a".to_string(),
1497                type_guid: Guid::from_bytes([1u8; 16]),
1498                instance_guid: Guid::from_bytes([1u8; 16]),
1499                start_block: 4,
1500                num_blocks: 2,
1501                flags: 0,
1502            },
1503            PartitionInfo {
1504                label: "b".to_string(),
1505                type_guid: Guid::from_bytes([2u8; 16]),
1506                instance_guid: Guid::from_bytes([2u8; 16]),
1507                start_block: 6,
1508                num_blocks: 200,
1509                flags: 0,
1510            },
1511        ];
1512        runner
1513            .reset_partition_table(new_partitions)
1514            .await
1515            .expect_err("reset_partition_table should fail");
1516
1517        runner.shutdown().await;
1518    }
1519
1520    #[fuchsia::test]
1521    async fn reset_partition_tables_fails_if_partition_overlaps_metadata() {
1522        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1523        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1524            .await
1525            .expect("load should succeed");
1526        let new_partitions = vec![PartitionInfo {
1527            label: "a".to_string(),
1528            type_guid: Guid::from_bytes([1u8; 16]),
1529            instance_guid: Guid::from_bytes([1u8; 16]),
1530            start_block: 1,
1531            num_blocks: 2,
1532            flags: 0,
1533        }];
1534        runner
1535            .reset_partition_table(new_partitions)
1536            .await
1537            .expect_err("reset_partition_table should fail");
1538
1539        runner.shutdown().await;
1540    }
1541
1542    #[fuchsia::test]
1543    async fn reset_partition_tables_fails_if_partitions_overlap() {
1544        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1545        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1546            .await
1547            .expect("load should succeed");
1548        let new_partitions = vec![
1549            PartitionInfo {
1550                label: "a".to_string(),
1551                type_guid: Guid::from_bytes([1u8; 16]),
1552                instance_guid: Guid::from_bytes([1u8; 16]),
1553                start_block: 32,
1554                num_blocks: 2,
1555                flags: 0,
1556            },
1557            PartitionInfo {
1558                label: "b".to_string(),
1559                type_guid: Guid::from_bytes([2u8; 16]),
1560                instance_guid: Guid::from_bytes([2u8; 16]),
1561                start_block: 33,
1562                num_blocks: 1,
1563                flags: 0,
1564            },
1565        ];
1566        runner
1567            .reset_partition_table(new_partitions)
1568            .await
1569            .expect_err("reset_partition_table should fail");
1570
1571        runner.shutdown().await;
1572    }
1573
1574    #[fuchsia::test]
1575    async fn add_partition() {
1576        let (block_device, partitions_dir) = setup(512, 64, vec![PartitionInfo::nil(); 64]).await;
1577        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1578            .await
1579            .expect("load should succeed");
1580
1581        let transaction = runner.create_transaction().await.expect("Create transaction failed");
1582        let request = fpartitions::PartitionsManagerAddPartitionRequest {
1583            transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1584            name: Some("a".to_string()),
1585            type_guid: Some(fblock::Guid { value: [1u8; 16] }),
1586            num_blocks: Some(2),
1587            ..Default::default()
1588        };
1589        runner.add_partition(request).await.expect("add_partition failed");
1590        runner.commit_transaction(transaction).await.expect("add_partition failed");
1591
1592        let proxy = vfs::serve_directory(
1593            partitions_dir.clone(),
1594            vfs::path::Path::validate_and_split("part-000").unwrap(),
1595            vfs::execution_scope::ExecutionScope::new(),
1596            fio::PERM_READABLE,
1597        );
1598        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1599            .expect("Failed to open block service");
1600        let client: RemoteBlockClient =
1601            RemoteBlockClient::new(block).await.expect("Failed to create block client");
1602
1603        assert_eq!(client.block_count(), 2);
1604        assert_eq!(client.block_size(), 512);
1605
1606        runner.shutdown().await;
1607    }
1608
1609    #[fuchsia::test]
1610    async fn partition_info() {
1611        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1612        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1613        const PART_NAME: &str = "part";
1614
1615        let (block_device, partitions_dir) = setup_with_options(
1616            VmoBackedServerOptions {
1617                initial_contents: InitialContents::FromCapacity(16),
1618                block_size: 512,
1619                info: DeviceInfo::Block(BlockInfo {
1620                    max_transfer_blocks: NonZero::new(2),
1621                    device_flags: BlockDeviceFlag::READONLY
1622                        | BlockDeviceFlag::REMOVABLE
1623                        | BlockDeviceFlag::ZSTD_DECOMPRESSION_SUPPORT,
1624                    ..Default::default()
1625                }),
1626                ..Default::default()
1627            },
1628            vec![PartitionInfo {
1629                label: PART_NAME.to_string(),
1630                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1631                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1632                start_block: 4,
1633                num_blocks: 1,
1634                flags: 0xabcd,
1635            }],
1636        )
1637        .await;
1638
1639        let partitions_dir_clone = partitions_dir.clone();
1640        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1641            .await
1642            .expect("load should succeed");
1643
1644        let part_dir = vfs::serve_directory(
1645            partitions_dir.clone(),
1646            vfs::path::Path::validate_and_split("part-000").unwrap(),
1647            vfs::execution_scope::ExecutionScope::new(),
1648            fio::PERM_READABLE,
1649        );
1650        let part_block =
1651            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1652                .expect("Failed to open Volume service");
1653        let info: fblock::BlockInfo =
1654            part_block.get_info().await.expect("FIDL error").expect("get_info failed");
1655        assert_eq!(info.block_count, 1);
1656        assert_eq!(info.block_size, 512);
1657        assert_eq!(
1658            info.flags,
1659            BlockDeviceFlag::READONLY
1660                | BlockDeviceFlag::REMOVABLE
1661                | BlockDeviceFlag::ZSTD_DECOMPRESSION_SUPPORT
1662        );
1663        assert_eq!(info.max_transfer_size, 1024);
1664
1665        let metadata: fblock::PartitionInfo =
1666            part_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1667        assert_eq!(metadata.name, Some(PART_NAME.to_string()));
1668        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1669        assert_eq!(metadata.instance_guid.unwrap().value, PART_INSTANCE_GUID);
1670        assert_eq!(metadata.start_block_offset, Some(4));
1671        assert_eq!(metadata.num_blocks, Some(1));
1672        assert_eq!(metadata.flags, Some(0xabcd));
1673
1674        runner.shutdown().await;
1675    }
1676
1677    #[fuchsia::test]
1678    async fn nested_gpt() {
1679        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1680        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1681        const PART_NAME: &str = "part";
1682
1683        let vmo = zx::Vmo::create(64 * 512).unwrap();
1684        let vmo_clone = vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap();
1685        let (outer_block_device, outer_partitions_dir) = setup_with_options(
1686            VmoBackedServerOptions {
1687                initial_contents: InitialContents::FromVmo(vmo_clone),
1688                block_size: 512,
1689                info: DeviceInfo::Block(BlockInfo {
1690                    device_flags: BlockDeviceFlag::READONLY | BlockDeviceFlag::REMOVABLE,
1691                    ..Default::default()
1692                }),
1693                ..Default::default()
1694            },
1695            vec![PartitionInfo {
1696                label: PART_NAME.to_string(),
1697                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1698                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1699                start_block: 4,
1700                num_blocks: 16,
1701                flags: 0xabcd,
1702            }],
1703        )
1704        .await;
1705
1706        let outer_partitions_dir_clone = outer_partitions_dir.clone();
1707        let outer_runner =
1708            GptManager::new(outer_block_device.connect(), outer_partitions_dir_clone)
1709                .await
1710                .expect("load should succeed");
1711
1712        let outer_part_dir = vfs::serve_directory(
1713            outer_partitions_dir.clone(),
1714            vfs::path::Path::validate_and_split("part-000").unwrap(),
1715            vfs::execution_scope::ExecutionScope::new(),
1716            fio::PERM_READABLE,
1717        );
1718        let part_block =
1719            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&outer_part_dir, "volume")
1720                .expect("Failed to open Block service");
1721
1722        let client = Arc::new(RemoteBlockClient::new(part_block.clone()).await.unwrap());
1723        let _ = gpt::Gpt::format(
1724            client,
1725            vec![PartitionInfo {
1726                label: PART_NAME.to_string(),
1727                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1728                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1729                start_block: 5,
1730                num_blocks: 1,
1731                flags: 0xabcd,
1732            }],
1733        )
1734        .await
1735        .unwrap();
1736
1737        let partitions_dir = vfs::directory::immutable::simple();
1738        let partitions_dir_clone = partitions_dir.clone();
1739        let runner =
1740            GptManager::new(part_block, partitions_dir_clone).await.expect("load should succeed");
1741        let part_dir = vfs::serve_directory(
1742            partitions_dir.clone(),
1743            vfs::path::Path::validate_and_split("part-000").unwrap(),
1744            vfs::execution_scope::ExecutionScope::new(),
1745            fio::PERM_READABLE,
1746        );
1747        let inner_part_block =
1748            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1749                .expect("Failed to open Block service");
1750
1751        let client =
1752            RemoteBlockClient::new(inner_part_block).await.expect("Failed to create block client");
1753        assert_eq!(client.block_count(), 1);
1754        assert_eq!(client.block_size(), 512);
1755
1756        let buffer = vec![0xaa; 512];
1757        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1758        client
1759            .write_at(BufferSlice::Memory(&buffer), 512)
1760            .await
1761            .expect_err("Write past end should fail");
1762        client.flush().await.unwrap();
1763
1764        runner.shutdown().await;
1765        outer_runner.shutdown().await;
1766
1767        // Check that the write targeted the correct block (4 + 5 = 9)
1768        let data = vmo.read_to_vec::<u8>(9 * 512, 512).unwrap();
1769        assert_eq!(&data[..], &buffer[..]);
1770    }
1771
1772    #[fuchsia::test]
1773    async fn open_session_with_options_is_rejected() {
1774        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1775        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1776        const PART_NAME: &str = "foo";
1777
1778        let (block_device, partitions_dir) = setup_with_options(
1779            VmoBackedServerOptions {
1780                initial_contents: InitialContents::FromCapacity(16),
1781                block_size: 512,
1782                ..Default::default()
1783            },
1784            vec![PartitionInfo {
1785                label: PART_NAME.to_string(),
1786                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1787                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1788                start_block: 4,
1789                num_blocks: 2,
1790                flags: 0xabcd,
1791            }],
1792        )
1793        .await;
1794
1795        let partitions_dir_clone = partitions_dir.clone();
1796        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1797            .await
1798            .expect("load should succeed");
1799
1800        let part_dir = vfs::serve_directory(
1801            partitions_dir.clone(),
1802            vfs::path::Path::validate_and_split("part-000").unwrap(),
1803            vfs::execution_scope::ExecutionScope::new(),
1804            fio::PERM_READABLE,
1805        );
1806
1807        let part_block =
1808            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1809                .expect("Failed to open Block service");
1810
1811        // Attempting to open a session with a valid offset map should fail.
1812        let (session, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
1813        part_block
1814            .open_session_with_options(
1815                server_end,
1816                &[fblock::BlockOffsetMapping { target_block_offset: 1, length: 2 }],
1817            )
1818            .expect("FIDL error");
1819        session
1820            .get_fifo()
1821            .await
1822            .expect_err("Session should be closed because nested mappings are not supported");
1823
1824        runner.shutdown().await;
1825    }
1826
1827    #[fuchsia::test]
1828    async fn test_open_session_with_options_rejects_nested_mappings() {
1829        let (block_device, partitions_dir) = setup(
1830            512,
1831            2048,
1832            vec![
1833                PartitionInfo {
1834                    label: "super".to_string(),
1835                    type_guid: Guid::from_bytes([1; 16]),
1836                    instance_guid: Guid::from_bytes([2; 16]),
1837                    start_block: 34,
1838                    num_blocks: 10,
1839                    flags: 0,
1840                },
1841                PartitionInfo {
1842                    label: "userdata".to_string(),
1843                    type_guid: Guid::from_bytes([1; 16]),
1844                    instance_guid: Guid::from_bytes([3; 16]),
1845                    start_block: 50,
1846                    num_blocks: 10,
1847                    flags: 0,
1848                },
1849            ],
1850        )
1851        .await;
1852
1853        let partitions_dir_clone = partitions_dir.clone();
1854        let runner = GptManager::new_with_config(
1855            block_device.connect(),
1856            partitions_dir_clone,
1857            crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
1858        )
1859        .await
1860        .expect("load should succeed");
1861
1862        let part_dir = vfs::serve_directory(
1863            partitions_dir.clone(),
1864            vfs::path::Path::validate_and_split("part-000").unwrap(),
1865            vfs::execution_scope::ExecutionScope::new(),
1866            fio::PERM_READABLE,
1867        );
1868
1869        let part_block =
1870            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1871                .expect("Failed to open Block service");
1872
1873        let metadata: fblock::PartitionInfo =
1874            part_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1875        assert_eq!(metadata.name, Some("super_and_userdata".to_string()));
1876        assert!(metadata.start_block_offset.is_none());
1877        assert!(metadata.flags.is_none());
1878
1879        // Attempting to open a session with an offset map on a merged GPT partition should fail
1880        // because it has static mappings, and nested mappings are not supported.
1881        let (session, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
1882        part_block
1883            .open_session_with_options(
1884                server_end,
1885                &[fblock::BlockOffsetMapping { target_block_offset: 0, length: 3 }],
1886            )
1887            .expect("FIDL error");
1888        session.get_fifo().await.expect_err("Session should be closed due to nested mapping");
1889
1890        {
1891            let inner = runner.inner.lock().await;
1892            let backend = inner.composite_partitions.get(&0).unwrap().session_manager().interface();
1893            assert!(backend.passthrough());
1894        }
1895
1896        runner.shutdown().await;
1897    }
1898
1899    #[fuchsia::test]
1900    async fn test_vmos_detached_on_session_close() {
1901        let (block_device, partitions_dir) = setup(
1902            512,
1903            100,
1904            vec![PartitionInfo {
1905                type_guid: Guid::from_bytes([2u8; 16]),
1906                instance_guid: Guid::from_bytes([2u8; 16]),
1907                start_block: 34,
1908                num_blocks: 10,
1909                flags: 0,
1910                label: "test".to_string(),
1911            }],
1912        )
1913        .await;
1914
1915        let runner = GptManager::new(block_device.connect(), partitions_dir.clone()).await.unwrap();
1916        let proxy = vfs::serve_directory(
1917            partitions_dir.clone(),
1918            vfs::path::Path::validate_and_split("part-000").unwrap(),
1919            vfs::execution_scope::ExecutionScope::new(),
1920            fio::PERM_READABLE,
1921        );
1922        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1923            .expect("Failed to open block service");
1924        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1925
1926        {
1927            let inner = runner.inner.lock().await;
1928            let backend = inner.partitions.get(&0).unwrap().session_manager().interface();
1929            assert_eq!(backend.vmo_count(), 1);
1930        }
1931
1932        client.close().await.expect("Failed to close client");
1933
1934        {
1935            let inner = runner.inner.lock().await;
1936            let backend = inner.partitions.get(&0).unwrap().session_manager().interface();
1937            assert_eq!(backend.vmo_count(), 0);
1938        }
1939
1940        runner.shutdown().await;
1941    }
1942
1943    #[test]
1944    fn test_should_passthrough_partition() {
1945        use super::{ALL_SYSTEM_PARTITION_LABELS, should_passthrough_partition};
1946
1947        let system_label = ALL_SYSTEM_PARTITION_LABELS[0].to_string();
1948
1949        // Single mapping on a system label partition -> should passthrough.
1950        let single_config = block_server::PartitionInfo {
1951            name: system_label.clone(),
1952            type_guid: [1; 16],
1953            instance_guid: [2; 16],
1954            flags: Some(0),
1955            start_block_offset: Some(0),
1956            block_count: 100,
1957            ..Default::default()
1958        };
1959        assert!(should_passthrough_partition(&single_config));
1960
1961        // Multiple mappings on the exact same system label partition -> should passthrough.
1962        let multi_config = block_server::PartitionInfo {
1963            name: system_label,
1964            type_guid: [1; 16],
1965            instance_guid: [2; 16],
1966            flags: Some(0),
1967            start_block_offset: Some(0),
1968            block_count: 200,
1969            ..Default::default()
1970        };
1971        assert!(should_passthrough_partition(&multi_config));
1972    }
1973
1974    #[fuchsia::test]
1975    async fn test_merged_partition_passthrough_behavior() {
1976        // Test Case 1: Discontiguous -> passthrough = false
1977        {
1978            let (block_device, partitions_dir) = setup(
1979                512,
1980                2048,
1981                vec![
1982                    PartitionInfo {
1983                        label: "super".to_string(),
1984                        type_guid: Guid::from_bytes([1; 16]),
1985                        instance_guid: Guid::from_bytes([2; 16]),
1986                        start_block: 34,
1987                        num_blocks: 10,
1988                        flags: 0,
1989                    },
1990                    PartitionInfo {
1991                        label: "userdata".to_string(),
1992                        type_guid: Guid::from_bytes([1; 16]),
1993                        instance_guid: Guid::from_bytes([3; 16]),
1994                        start_block: 50, // Discontiguous (44 != 50)
1995                        num_blocks: 10,
1996                        flags: 0,
1997                    },
1998                ],
1999            )
2000            .await;
2001
2002            let runner = GptManager::new_with_config(
2003                block_device.connect(),
2004                partitions_dir,
2005                crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
2006            )
2007            .await
2008            .expect("load should succeed");
2009
2010            {
2011                let inner = runner.inner.lock().await;
2012                let backend =
2013                    inner.composite_partitions.get(&0).unwrap().session_manager().interface();
2014                assert!(backend.passthrough());
2015            }
2016            runner.shutdown().await;
2017        }
2018
2019        // Test Case 2: Contiguous -> passthrough = true (after coalescing it will be 1 mapping)
2020        {
2021            let (block_device, partitions_dir) = setup(
2022                512,
2023                2048,
2024                vec![
2025                    PartitionInfo {
2026                        label: "super".to_string(),
2027                        type_guid: Guid::from_bytes([1; 16]),
2028                        instance_guid: Guid::from_bytes([2; 16]),
2029                        start_block: 34,
2030                        num_blocks: 10,
2031                        flags: 0,
2032                    },
2033                    PartitionInfo {
2034                        label: "userdata".to_string(),
2035                        type_guid: Guid::from_bytes([1; 16]),
2036                        instance_guid: Guid::from_bytes([3; 16]),
2037                        start_block: 44, // Contiguous (34 + 10 = 44)
2038                        num_blocks: 10,
2039                        flags: 0,
2040                    },
2041                ],
2042            )
2043            .await;
2044
2045            let runner = GptManager::new_with_config(
2046                block_device.connect(),
2047                partitions_dir,
2048                crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
2049            )
2050            .await
2051            .expect("load should succeed");
2052
2053            {
2054                let inner = runner.inner.lock().await;
2055                let backend =
2056                    inner.composite_partitions.get(&0).unwrap().session_manager().interface();
2057                assert!(backend.passthrough());
2058            }
2059            runner.shutdown().await;
2060        }
2061    }
2062}