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