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