Skip to main content

gpt_component/
gpt.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::config::Config;
6use crate::partition::PartitionBackend;
7use crate::partitions_directory::PartitionsDirectory;
8use anyhow::{Context as _, Error, ensure};
9use block_client::{
10    BlockClient as _, BufferSlice, MutableBufferSlice, ReadOptions, RemoteBlockClient, VmoId,
11    WriteOptions,
12};
13use block_server::async_interface::SessionManager;
14use block_server::{BlockServer, OffsetMap};
15
16use fidl::endpoints::ServerEnd;
17use fidl_fuchsia_storage_block as fblock;
18use fidl_fuchsia_storage_partitions as fpartitions;
19use fs_management::format::constants::{
20    ALL_BENCHMARK_PARTITION_LABELS, ALL_SYSTEM_PARTITION_LABELS,
21};
22use fuchsia_async as fasync;
23use fuchsia_sync::Mutex;
24use futures::stream::TryStreamExt as _;
25use std::collections::BTreeMap;
26use std::num::NonZero;
27use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
28use std::sync::{Arc, OnceLock, Weak};
29
30fn partition_directory_entry_name(index: u32) -> String {
31    format!("part-{:03}", index)
32}
33
34// We use heuristics to decide which partitions to pass through.
35// Partitions which are passed through consume more resources on the underlying block device (e.g. a
36// dedicated per-session thread in some implementations), but have better performance due to not
37// needing to proxy requests through this component.  As such, the idea is that we only pass through
38// "hot" partitions.
39// This list should stay small.
40fn should_passthrough_partition(info: &block_server::PartitionInfo) -> bool {
41    // Partition contains the main filesystem
42    ALL_SYSTEM_PARTITION_LABELS.contains(&info.name.as_str())
43    // Partitions are used for benchmarks which should replicate the performance
44    // of the main filesystem
45    || ALL_BENCHMARK_PARTITION_LABELS.contains(&info.name.as_str())
46    // We always pass through composite partitions, since the mechanism we use for passthrough is to
47    // open sessions with an OffsetMap.  We could locally resolve the offsets, but there's no reason
48    // to implement that right now.
49    || info.start_block_offset.is_none()
50}
51
52fn single_partition_mapping(info: &block_server::PartitionInfo) -> Result<OffsetMap, Error> {
53    Ok(OffsetMap::new(vec![block_server::BlockOffsetMapping {
54        target_block_offset: info.start_block_offset.ok_or(zx::Status::INVALID_ARGS)?,
55        length: info.block_count,
56    }])?)
57}
58
59/// A single partition in a GPT device.
60pub struct GptPartition {
61    gpt: Weak<GptManager>,
62    info: Mutex<block_server::PartitionInfo>,
63    block_client: Arc<RemoteBlockClient>,
64}
65
66fn trace_id(trace_flow_id: Option<NonZero<u64>>) -> u64 {
67    trace_flow_id.map(|v| v.get()).unwrap_or_default()
68}
69
70impl GptPartition {
71    pub fn new(
72        gpt: &Arc<GptManager>,
73        block_client: Arc<RemoteBlockClient>,
74        info: block_server::PartitionInfo,
75    ) -> Arc<Self> {
76        Arc::new(Self { gpt: Arc::downgrade(gpt), info: Mutex::new(info), block_client })
77    }
78
79    pub async fn terminate(&self) {
80        if let Err(error) = self.block_client.close().await {
81            log::warn!(error:?; "Failed to close block client");
82        }
83    }
84
85    pub fn update_info(&self, info: gpt::PartitionInfo) {
86        *self.info.lock() = info.into();
87    }
88
89    pub fn block_size(&self) -> u32 {
90        self.block_client.block_size()
91    }
92
93    pub fn block_count(&self) -> u64 {
94        self.info.lock().block_count
95    }
96
97    /// Attaches the VMO.
98    ///
99    /// # Safety
100    ///
101    /// The caller must guarantee that the VMO is only attached once.  The reason for this is that
102    /// if the far end suddenly disconnects, it is not safe to assume the VMO will not be written to
103    /// in any way: the VMO could be the target of an ongoing DMA transfer.
104    ///
105    /// The caller must also ensure that no references are held during I/O as this would be
106    /// undefined behavior.  The caller may hold pointers, which does not lead to undefined
107    /// behavior; Rust does not make the same assumptions as references for pointers.
108    pub async unsafe fn attach_vmo(&self, vmo: &zx::Vmo) -> Result<VmoId, zx::Status> {
109        // SAFETY: The caller must guarantee that the VMO is only attached once and no references
110        // are held during I/O.
111        unsafe { self.block_client.attach_vmo(vmo) }.await
112    }
113
114    pub async fn detach_vmo(&self, vmoid: VmoId) -> Result<(), zx::Status> {
115        self.block_client.detach_vmo(vmoid).await
116    }
117
118    pub fn open_passthrough_session(
119        &self,
120        session: ServerEnd<fblock::SessionMarker>,
121        offset_map: &OffsetMap,
122    ) {
123        if let Some(gpt) = self.gpt.upgrade() {
124            let mappings: Vec<fblock::BlockOffsetMapping> = offset_map.into();
125            if let Err(error) = gpt.block_proxy.open_session_with_options(session, &mappings[..]) {
126                // Client errors normally come back on `session` but that was already consumed.  The
127                // client will get a PEER_CLOSED without an epitaph.
128                log::warn!(error:?; "Failed to open passthrough session");
129            }
130        } else {
131            if let Err(error) = session.close_with_epitaph(zx::Status::BAD_STATE) {
132                log::warn!(error:?; "Failed to send session epitaph");
133            }
134        }
135    }
136
137    /// Returns the parent [`GptManager`] if it is still running.
138    pub fn gpt(&self) -> Option<Arc<GptManager>> {
139        self.gpt.upgrade()
140    }
141
142    pub fn get_info(&self) -> block_server::DeviceInfo {
143        let mut info = self.info.lock().clone();
144        info.device_flags = self.block_client.block_flags();
145        info.max_transfer_blocks = self.block_client.max_transfer_blocks();
146        block_server::DeviceInfo::Partition(info)
147    }
148
149    pub async fn read(
150        &self,
151        device_block_offset: u64,
152        block_count: u32,
153        vmo_id: &VmoId,
154        vmo_offset: u64, // *bytes* not blocks
155        opts: ReadOptions,
156        trace_flow_id: Option<NonZero<u64>>,
157    ) -> Result<(), zx::Status> {
158        let dev_offset = self
159            .absolute_offset(device_block_offset, block_count)
160            .map(|offset| offset * self.block_size() as u64)?;
161        let buffer = MutableBufferSlice::new_with_vmo_id(
162            vmo_id,
163            vmo_offset,
164            (block_count * self.block_size()) as u64,
165        );
166        self.block_client
167            .read_at_with_opts_traced(buffer, dev_offset, opts, trace_id(trace_flow_id))
168            .await
169    }
170
171    pub async fn write(
172        &self,
173        device_block_offset: u64,
174        block_count: u32,
175        vmo_id: &VmoId,
176        vmo_offset: u64, // *bytes* not blocks
177        opts: WriteOptions,
178        trace_flow_id: Option<NonZero<u64>>,
179    ) -> Result<(), zx::Status> {
180        let dev_offset = self
181            .absolute_offset(device_block_offset, block_count)
182            .map(|offset| offset * self.block_size() as u64)?;
183        let buffer = BufferSlice::new_with_vmo_id(
184            vmo_id,
185            vmo_offset,
186            (block_count * self.block_size()) as u64,
187        );
188        self.block_client
189            .write_at_with_opts_traced(buffer, dev_offset, opts, trace_id(trace_flow_id))
190            .await
191    }
192
193    pub async fn flush(&self, trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
194        self.block_client.flush_traced(trace_id(trace_flow_id)).await
195    }
196
197    pub async fn trim(
198        &self,
199        device_block_offset: u64,
200        block_count: u32,
201        trace_flow_id: Option<NonZero<u64>>,
202    ) -> Result<(), zx::Status> {
203        let dev_offset = self
204            .absolute_offset(device_block_offset, block_count)
205            .map(|offset| offset * self.block_size() as u64)?;
206        let len = block_count as u64 * self.block_size() as u64;
207        let end = dev_offset.checked_add(len).ok_or(zx::Status::OUT_OF_RANGE)?;
208
209        self.block_client.trim_traced(dev_offset..end, trace_id(trace_flow_id)).await
210    }
211
212    // Converts a relative range specified by [offset, offset+len) into an absolute offset in the
213    // GPT device, performing bounds checking within the partition.  Returns ZX_ERR_OUT_OF_RANGE for
214    // an invalid offset/len.
215    fn absolute_offset(&self, mut offset: u64, len: u32) -> Result<u64, zx::Status> {
216        let info = self.info.lock();
217        let Some(start_block) = info.start_block_offset else {
218            // This indicates that a composite partition was not passed through, which is an error
219            // in this library.
220            return Err(zx::Status::BAD_STATE);
221        };
222        offset = offset.checked_add(start_block).ok_or(zx::Status::OUT_OF_RANGE)?;
223        let end = offset.checked_add(len as u64).ok_or(zx::Status::OUT_OF_RANGE)?;
224        if end > start_block + info.block_count {
225            Err(zx::Status::OUT_OF_RANGE)
226        } else {
227            Ok(offset)
228        }
229    }
230}
231
232struct PendingTransaction {
233    transaction: gpt::Transaction,
234    client_koid: zx::Koid,
235    // A list of indexes for partitions which were added in the transaction.  When committing, all
236    // newly created partitions are published.
237    added_partitions: Vec<u32>,
238    // A task which waits for the client end to be closed and clears the pending transaction.
239    _signal_task: fasync::Task<()>,
240}
241
242struct Inner {
243    gpt: gpt::Gpt,
244    partitions: BTreeMap<u32, Arc<BlockServer<SessionManager<PartitionBackend>>>>,
245    // We track these separately so that we do not update them during transaction commit.
246    composite_partitions: BTreeMap<u32, Arc<BlockServer<SessionManager<PartitionBackend>>>>,
247    // Exposes all partitions for discovery by other components.  Should be kept in sync with
248    // `partitions`.
249    partitions_dir: PartitionsDirectory,
250    pending_transaction: Option<PendingTransaction>,
251}
252
253impl Inner {
254    /// Ensures that `transaction` matches our pending transaction.
255    fn ensure_transaction_matches(&self, transaction: &zx::EventPair) -> Result<(), zx::Status> {
256        if let Some(pending) = self.pending_transaction.as_ref() {
257            if transaction.koid()? == pending.client_koid {
258                Ok(())
259            } else {
260                Err(zx::Status::BAD_HANDLE)
261            }
262        } else {
263            Err(zx::Status::BAD_STATE)
264        }
265    }
266
267    fn bind_partition(
268        &mut self,
269        parent: &Arc<GptManager>,
270        index: u32,
271        info: block_server::PartitionInfo,
272        composite_mappings: OffsetMap,
273        composite_indexes: Vec<usize>,
274    ) -> Result<(), Error> {
275        ensure!(
276            composite_indexes.is_empty() == composite_mappings.is_empty(),
277            "Composite partitions must provide mappings"
278        );
279        let passthrough = should_passthrough_partition(&info);
280        let mappings = if passthrough && composite_mappings.is_empty() {
281            // Synthesize a mapping for a non-composite passthrough partition.
282            single_partition_mapping(&info)?
283        } else {
284            // Either this is a composite partition which already has a mapping, or it is a
285            // non-composite partition which is not passed through (in which case this is an empty
286            // mapping).
287            composite_mappings
288        };
289        log::debug!(
290            "GPT part {index}{}{}: {info:?}",
291            if !composite_indexes.is_empty() { " (composite)" } else { "" },
292            if passthrough { " (passthrough)" } else { "" },
293        );
294        let partition = PartitionBackend::new(
295            GptPartition::new(parent, self.gpt.client().clone(), info),
296            mappings,
297        );
298        let block_server = Arc::new(BlockServer::new(parent.block_size, partition));
299        if !composite_indexes.is_empty() {
300            self.partitions_dir.add_composite(
301                &partition_directory_entry_name(index),
302                Arc::downgrade(&block_server),
303                Arc::downgrade(parent),
304                composite_indexes,
305            );
306            self.composite_partitions.insert(index, block_server);
307        } else {
308            self.partitions_dir.add_partition(
309                &partition_directory_entry_name(index),
310                Arc::downgrade(&block_server),
311                Arc::downgrade(parent),
312                index as usize,
313            );
314            self.partitions.insert(index, block_server);
315        }
316        Ok(())
317    }
318
319    fn bind_super_and_userdata_partition(
320        &mut self,
321        parent: &Arc<GptManager>,
322        super_partition: (u32, gpt::PartitionInfo),
323        userdata_partition: (u32, gpt::PartitionInfo),
324    ) -> Result<(), Error> {
325        let extent1 = block_server::BlockOffsetMapping {
326            target_block_offset: super_partition.1.start_block,
327            length: super_partition.1.num_blocks,
328        };
329        let extent2 = block_server::BlockOffsetMapping {
330            target_block_offset: userdata_partition.1.start_block,
331            length: userdata_partition.1.num_blocks,
332        };
333        let mappings =
334            block_server::OffsetMap::new(block_server::coalesce_mappings(vec![extent1, extent2]))?;
335        let info = block_server::PartitionInfo {
336            // TODO(https://fxbug.dev/443980711): This should come from configuration.
337            name: "super_and_userdata".to_string(),
338            type_guid: super_partition.1.type_guid.to_bytes(),
339            instance_guid: super_partition.1.instance_guid.to_bytes(),
340            block_count: mappings.total_blocks(),
341            ..Default::default()
342        };
343        log::debug!(
344            "GPT merged parts {:?} + {:?} -> {info:?}",
345            super_partition.1,
346            userdata_partition.1
347        );
348        self.bind_partition(
349            parent,
350            super_partition.0,
351            info,
352            mappings,
353            vec![super_partition.0 as usize, userdata_partition.0 as usize],
354        )
355    }
356
357    async fn bind_all_partitions(&mut self, parent: &Arc<GptManager>) -> Result<(), Error> {
358        self.partitions.clear();
359        self.composite_partitions.clear();
360        self.partitions_dir.clear().await;
361
362        let mut partitions = self.gpt.partitions().clone();
363        if parent.config.merge_super_and_userdata {
364            // Attempt to merge the first `super` and `userdata` we find.  The rest will be treated
365            // as regular partitions.
366            let super_part = match partitions
367                .iter()
368                .find(|(_, info)| info.label == "super")
369                .map(|(index, _)| *index)
370            {
371                Some(index) => partitions.remove_entry(&index),
372                None => None,
373            };
374            let userdata_part = match partitions
375                .iter()
376                .find(|(_, info)| info.label == "userdata")
377                .map(|(index, _)| *index)
378            {
379                Some(index) => partitions.remove_entry(&index),
380                None => None,
381            };
382            if super_part.is_some() && userdata_part.is_some() {
383                let super_part = super_part.unwrap();
384                let userdata_part = userdata_part.unwrap();
385                self.bind_super_and_userdata_partition(parent, super_part, userdata_part)?;
386            } else if super_part.is_some() || userdata_part.is_some() {
387                log::warn!("Only one of super/userdata found; not merging");
388                let (index, info) = super_part.or(userdata_part).unwrap();
389                self.bind_partition(
390                    parent,
391                    index,
392                    block_server::PartitionInfo::from(&info),
393                    OffsetMap::empty(),
394                    vec![],
395                )?;
396            }
397        }
398        for (index, info) in partitions {
399            self.bind_partition(
400                parent,
401                index,
402                block_server::PartitionInfo::from(&info),
403                OffsetMap::empty(),
404                vec![],
405            )?;
406        }
407        Ok(())
408    }
409
410    fn add_partition(&mut self, info: gpt::PartitionInfo) -> Result<usize, gpt::AddPartitionError> {
411        let pending = self.pending_transaction.as_mut().unwrap();
412        let idx = self.gpt.add_partition(&mut pending.transaction, info)?;
413        pending.added_partitions.push(idx as u32);
414        Ok(idx)
415    }
416}
417
418/// Encodes partition `offset_map` into raw extent bytes for the mapping VMO FIFO.
419///
420/// Returns the encoded payload bytes, total uncompressed size, extent count, and base device
421/// offset.
422fn offset_map_to_extents(
423    offset_map: &OffsetMap,
424    block_size: u32,
425) -> Result<(Vec<u8>, u64, u32, u64), Error> {
426    let mappings = offset_map.mappings();
427    let block_size = block_size as u64;
428    let base_device_offset =
429        mappings.iter().map(|m| m.target_block_offset * block_size).min().unwrap_or(0);
430    let mut running_logical = 0u64;
431    let mut extents = Vec::with_capacity(mappings.len());
432    let num_mappings = mappings.len();
433    for (i, m) in mappings.iter().enumerate() {
434        let is_last = i == num_mappings - 1;
435        let total_bytes = m.length * block_size;
436        let len_bytes = if is_last {
437            // Because we only support 4 KiB mappings, we can support misalignment if it's the last
438            // range (because the unaligned tail wouldn't be usable anyway).
439            total_bytes - (total_bytes % mapping::BLOCK_SIZE)
440        } else {
441            // Because we only support 4 KiB mappings, we cannot handle misalignment across two
442            // adjacent ranges.
443            ensure!(total_bytes % mapping::BLOCK_SIZE == 0, zx::Status::NOT_SUPPORTED);
444            total_bytes
445        };
446        if len_bytes == 0 {
447            continue;
448        }
449        let dev_offset_bytes = m.target_block_offset * block_size;
450        let extent = mapping::Extent::new(
451            running_logical..running_logical + len_bytes,
452            Some(dev_offset_bytes),
453        );
454        running_logical += len_bytes;
455        extents.push(extent);
456    }
457    let extents = mapping::Extents::try_new(extents, base_device_offset)?;
458    let mut payload_bytes = Vec::with_capacity(mappings.len() * std::mem::size_of::<u64>());
459    let mut extent_count = 0u32;
460    for w in mapping::Extents::encode_extents_with_base_offset(&extents) {
461        payload_bytes.extend_from_slice(&w.to_le_bytes());
462        extent_count += 1;
463    }
464    Ok((payload_bytes, running_logical, extent_count, base_device_offset))
465}
466
467struct MapperSessionState {
468    session_proxy: fblock::MapperSessionProxy,
469    sender: futures::lock::Mutex<vmo_fifo::AsyncSender<mapping::RawMappingCommand>>,
470}
471
472/// Runs a GPT device.
473pub struct GptManager {
474    config: Config,
475    block_proxy: fblock::BlockProxy,
476    mapper_proxy: Option<fblock::MapperProxy>,
477    mapper_session: OnceLock<MapperSessionState>,
478    next_partition_key: AtomicU64,
479    block_size: u32,
480    block_count: u64,
481    inner: futures::lock::Mutex<Inner>,
482    shutdown: AtomicBool,
483}
484
485impl std::fmt::Debug for GptManager {
486    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
487        f.debug_struct("GptManager")
488            .field("block_size", &self.block_size)
489            .field("block_count", &self.block_count)
490            .finish()
491    }
492}
493
494impl GptManager {
495    pub async fn new(
496        block_proxy: fblock::BlockProxy,
497        partitions_dir: Arc<vfs::directory::immutable::Simple>,
498    ) -> Result<Arc<Self>, Error> {
499        Self::new_with_config(block_proxy, partitions_dir, Config::default()).await
500    }
501
502    /// Creates a new [`GptManager`] with an optional mapper proxy and default configuration.
503    pub async fn new_with_mapper(
504        block_proxy: fblock::BlockProxy,
505        mapper_proxy: Option<fblock::MapperProxy>,
506        partitions_dir: Arc<vfs::directory::immutable::Simple>,
507    ) -> Result<Arc<Self>, Error> {
508        Self::new_with_config_and_mapper(
509            block_proxy,
510            mapper_proxy,
511            partitions_dir,
512            Config::default(),
513        )
514        .await
515    }
516
517    pub async fn new_with_config(
518        block_proxy: fblock::BlockProxy,
519        partitions_dir: Arc<vfs::directory::immutable::Simple>,
520        config: Config,
521    ) -> Result<Arc<Self>, Error> {
522        Self::new_with_config_and_mapper(block_proxy, None, partitions_dir, config).await
523    }
524
525    /// Creates a new [`GptManager`] with custom config and an optional mapper proxy.
526    pub async fn new_with_config_and_mapper(
527        block_proxy: fblock::BlockProxy,
528        mapper_proxy: Option<fblock::MapperProxy>,
529        partitions_dir: Arc<vfs::directory::immutable::Simple>,
530        config: Config,
531    ) -> Result<Arc<Self>, Error> {
532        log::info!("Binding to GPT");
533        let client = Arc::new(RemoteBlockClient::new(block_proxy.clone()).await?);
534        let block_size = client.block_size();
535        let block_count = client.block_count();
536        let gpt = gpt::Gpt::open(client).await.context("Failed to load GPT")?;
537
538        let mapper_proxy = match mapper_proxy {
539            Some(proxy) => Some(proxy),
540            None => {
541                let (proxy, server) = fidl::endpoints::create_proxy::<fblock::MapperMarker>();
542                if block_proxy.connect_mapper(server).await.is_ok_and(|res| res.is_ok()) {
543                    Some(proxy)
544                } else {
545                    None
546                }
547            }
548        };
549
550        let this = Arc::new(Self {
551            config,
552            block_proxy,
553            mapper_proxy,
554            mapper_session: OnceLock::new(),
555            next_partition_key: AtomicU64::new(1),
556            block_size,
557            block_count,
558            inner: futures::lock::Mutex::new(Inner {
559                gpt,
560                partitions: BTreeMap::new(),
561                composite_partitions: BTreeMap::new(),
562                partitions_dir: PartitionsDirectory::new(partitions_dir),
563                pending_transaction: None,
564            }),
565            shutdown: AtomicBool::new(false),
566        });
567        this.inner.lock().await.bind_all_partitions(&this).await?;
568        log::info!("Starting all partitions OK!");
569        Ok(this)
570    }
571
572    /// Returns the initialized [`MapperSessionState`] for the parent device's mapper session,
573    /// lazily opening the session on first access.
574    ///
575    /// # Panics
576    ///
577    /// Panics if the parent device was not configured with a mapper proxy.
578    async fn mapper_state(&self) -> &MapperSessionState {
579        let mapper_proxy = self.mapper_proxy.as_ref().unwrap();
580
581        let mut init_server = None;
582        let state = self.mapper_session.get_or_init(|| {
583            let (session_proxy, session_server) =
584                fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
585            let parent_mapping_vmo = zx::Vmo::create(mapping::MAPPING_VMO_SIZE).unwrap();
586            let sender = vmo_fifo::AsyncSender::<mapping::RawMappingCommand>::new(
587                parent_mapping_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
588                1024,
589                mapping::PENDING_COMMANDS_CAPACITY,
590            )
591            .unwrap();
592            init_server = Some((session_server, parent_mapping_vmo));
593            MapperSessionState { session_proxy, sender: futures::lock::Mutex::new(sender) }
594        });
595        // Note: A concurrent caller may see `self.mapper_session` as already initialized and
596        // return `state` while the initializing thread is still awaiting `open_session`. This is
597        // fine because FIDL channel message ordering ensures subsequent requests on `session_proxy`
598        // are processed after the session is opened, and any failure will close the channel.
599        if let Some((session_server, parent_mapping_vmo)) = init_server {
600            match mapper_proxy.open_session(session_server, parent_mapping_vmo, None, None).await {
601                Ok(Err(status)) => {
602                    log::warn!(
603                        status:? = zx::Status::err_from_raw(status);
604                        "Failed to open mapper session on parent device"
605                    );
606                }
607                Err(error) => {
608                    log::warn!(error:?; "FIDL error calling Mapper.OpenSession on parent device");
609                }
610                Ok(Ok(())) => {}
611            }
612        }
613        state
614    }
615
616    /// Returns a reference to the parent device's [`fblock::MapperSessionProxy`].
617    ///
618    /// # Panics
619    ///
620    /// Panics if the parent device was not configured with a mapper proxy.
621    pub async fn mapper_session_proxy(&self) -> &fblock::MapperSessionProxy {
622        &self.mapper_state().await.session_proxy
623    }
624
625    /// Allocates and returns the next unique partition key for mapper sessions.
626    pub fn next_partition_key(&self) -> u64 {
627        self.next_partition_key.fetch_add(1, Ordering::Relaxed)
628    }
629
630    /// Registers the extent mapping for a partition under `key` with the parent mapper session.
631    ///
632    /// # Panics
633    ///
634    /// Panics if the parent device was not configured with a mapper proxy.
635    pub async fn register_mappings(&self, key: u64, offset_map: &OffsetMap) -> Result<(), Error> {
636        let state = self.mapper_state().await;
637        let mut sender = state.sender.lock().await;
638
639        let (payload_bytes, stored_size, extent_count, device_offset) =
640            offset_map_to_extents(offset_map, self.block_size)?;
641        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).await?;
642        let cmd = mapping::RawMappingCommand {
643            opcode: mapping::MAPPINGS_COMMAND,
644            offset: payload_buf.offset(),
645            key,
646            stored_size,
647            device_offset,
648            metadata_count: 0,
649            extent_count,
650        };
651        payload_buf.data().copy_from_slice(&payload_bytes);
652        payload_buf.commit(cmd).await?;
653        Ok(())
654    }
655
656    /// Returns `true` if this GPT instance was configured with a mapper proxy.
657    pub fn has_mapper(&self) -> bool {
658        self.mapper_proxy.is_some()
659    }
660
661    pub fn block_size(&self) -> u32 {
662        self.block_size
663    }
664
665    pub fn block_count(&self) -> u64 {
666        self.block_count
667    }
668
669    pub async fn create_transaction(self: &Arc<Self>) -> Result<zx::EventPair, zx::Status> {
670        let mut inner = self.inner.lock().await;
671        if inner.pending_transaction.is_some() {
672            return Err(zx::Status::ALREADY_EXISTS);
673        }
674        let transaction = inner.gpt.create_transaction().unwrap();
675        let (client_end, server_end) = zx::EventPair::create();
676        let client_koid = client_end.koid()?;
677        let signal_waiter = fasync::OnSignals::new(server_end, zx::Signals::EVENTPAIR_PEER_CLOSED);
678        let this = self.clone();
679        let task = fasync::Task::spawn(async move {
680            let _ = signal_waiter.await;
681            let mut inner = this.inner.lock().await;
682            if inner.pending_transaction.as_ref().map_or(false, |t| t.client_koid == client_koid) {
683                inner.pending_transaction = None;
684            }
685        });
686        inner.pending_transaction = Some(PendingTransaction {
687            transaction,
688            client_koid,
689            added_partitions: vec![],
690            _signal_task: task,
691        });
692        Ok(client_end)
693    }
694
695    pub async fn commit_transaction(
696        self: &Arc<Self>,
697        transaction: zx::EventPair,
698    ) -> Result<(), zx::Status> {
699        let mut inner = self.inner.lock().await;
700        inner.ensure_transaction_matches(&transaction)?;
701        let pending = std::mem::take(&mut inner.pending_transaction).unwrap();
702        let partitions = pending.transaction.partitions.clone();
703        if let Err(error) = inner.gpt.commit_transaction(pending.transaction).await {
704            log::warn!(error:?; "Failed to commit transaction");
705            return Err(zx::Status::IO);
706        }
707        // Everything after this point should be infallible.
708        for (info, idx) in partitions
709            .iter()
710            .zip(0u32..)
711            .filter(|(info, idx)| !info.is_nil() && !pending.added_partitions.contains(idx))
712        {
713            // Some physical partitions are not tracked in `inner.partitions` (e.g. when we use an
714            // composite partition to combine two physical partitions).  In this case, we still need
715            // to propagate the info in the underlying transaction, but there's no need to update
716            // the in-memory info.
717            // Note that composite partitions can't be changed by transactions anyways, so the info
718            // we propagate should be exactly what it was when we created the transaction.
719            if let Some(part) = inner.partitions.get(&idx) {
720                part.session_manager().interface().update_info(info.clone());
721            }
722        }
723        for idx in pending.added_partitions {
724            if let Some(gpt_info) = inner.gpt.partitions().get(&idx).cloned() {
725                let partition_info = block_server::PartitionInfo::from(&gpt_info);
726                if let Err(error) =
727                    inner.bind_partition(self, idx, partition_info, OffsetMap::empty(), vec![])
728                {
729                    log::error!(error:?; "Failed to bind partition");
730                }
731            }
732        }
733        Ok(())
734    }
735
736    pub async fn add_partition(
737        &self,
738        request: fpartitions::PartitionsManagerAddPartitionRequest,
739    ) -> Result<(), zx::Status> {
740        let mut inner = self.inner.lock().await;
741        inner.ensure_transaction_matches(
742            request.transaction.as_ref().ok_or(zx::Status::BAD_HANDLE)?,
743        )?;
744        let info = gpt::PartitionInfo {
745            label: request.name.ok_or(zx::Status::INVALID_ARGS)?,
746            type_guid: request
747                .type_guid
748                .map(|value| gpt::Guid::from_bytes(value.value))
749                .ok_or(zx::Status::INVALID_ARGS)?,
750            instance_guid: request
751                .instance_guid
752                .map(|value| gpt::Guid::from_bytes(value.value))
753                .unwrap_or_else(|| gpt::Guid::generate()),
754            start_block: 0,
755            num_blocks: request.num_blocks.ok_or(zx::Status::INVALID_ARGS)?,
756            flags: request.flags.unwrap_or_default(),
757        };
758        let idx = inner.add_partition(info)?;
759        let partition =
760            inner.pending_transaction.as_ref().unwrap().transaction.partitions.get(idx).unwrap();
761        log::info!(
762            "Allocated partition {:?} at {:?}",
763            partition.label,
764            partition.start_block..partition.start_block + partition.num_blocks
765        );
766        Ok(())
767    }
768
769    pub async fn handle_partitions_requests(
770        &self,
771        gpt_index: usize,
772        mut requests: fpartitions::PartitionRequestStream,
773    ) -> Result<(), zx::Status> {
774        while let Some(request) = requests.try_next().await.unwrap() {
775            match request {
776                fpartitions::PartitionRequest::UpdateMetadata { payload, responder } => {
777                    responder
778                        .send(
779                            self.update_partition_metadata(gpt_index, payload)
780                                .await
781                                .map_err(|status| status.into_raw()),
782                        )
783                        .unwrap_or_else(
784                            |error| log::error!(error:?; "Failed to send UpdateMetadata response"),
785                        );
786                }
787            }
788        }
789        Ok(())
790    }
791
792    async fn update_partition_metadata(
793        &self,
794        gpt_index: usize,
795        request: fpartitions::PartitionUpdateMetadataRequest,
796    ) -> Result<(), zx::Status> {
797        let mut inner = self.inner.lock().await;
798        inner.ensure_transaction_matches(
799            request.transaction.as_ref().ok_or(zx::Status::BAD_HANDLE)?,
800        )?;
801
802        let transaction = &mut inner.pending_transaction.as_mut().unwrap().transaction;
803        let entry = transaction.partitions.get_mut(gpt_index).ok_or(zx::Status::BAD_STATE)?;
804        if let Some(type_guid) = request.type_guid.as_ref().cloned() {
805            entry.type_guid = gpt::Guid::from_bytes(type_guid.value);
806        }
807        if let Some(flags) = request.flags.as_ref() {
808            entry.flags = *flags;
809        }
810        Ok(())
811    }
812
813    pub async fn handle_composite_partitions_requests(
814        &self,
815        gpt_indexes: Vec<usize>,
816        mut requests: fpartitions::OverlayPartitionRequestStream,
817    ) -> Result<(), zx::Status> {
818        while let Some(request) = requests.try_next().await.unwrap() {
819            match request {
820                fpartitions::OverlayPartitionRequest::GetPartitions { responder } => {
821                    match self.get_composite_partition_info(&gpt_indexes[..]).await {
822                        Ok(partitions) => responder.send(Ok(&partitions[..])),
823                        Err(status) => responder.send(Err(status.into_raw())),
824                    }
825                    .unwrap_or_else(
826                        |error| log::error!(error:?; "Failed to send GetPartitions response"),
827                    );
828                }
829            }
830        }
831        Ok(())
832    }
833
834    async fn get_composite_partition_info(
835        &self,
836        gpt_indexes: &[usize],
837    ) -> Result<Vec<fpartitions::PartitionInfo>, zx::Status> {
838        fn convert_partition_info(info: &gpt::PartitionInfo) -> fpartitions::PartitionInfo {
839            fpartitions::PartitionInfo {
840                name: Some(info.label.to_string()),
841                type_guid: Some(fblock::Guid { value: info.type_guid.to_bytes() }),
842                instance_guid: Some(fblock::Guid { value: info.instance_guid.to_bytes() }),
843                start_block_offset: Some(info.start_block),
844                num_blocks: Some(info.num_blocks),
845                flags: Some(info.flags),
846                ..Default::default()
847            }
848        }
849
850        let inner = self.inner.lock().await;
851        let mut partitions = vec![];
852        for index in gpt_indexes {
853            let index: u32 = *index as u32;
854            partitions.push(
855                inner
856                    .gpt
857                    .partitions()
858                    .get(&index)
859                    .map(convert_partition_info)
860                    .ok_or(zx::Status::BAD_STATE)?,
861            );
862        }
863        Ok(partitions)
864    }
865
866    pub async fn reset_partition_table(
867        self: &Arc<Self>,
868        partitions: Vec<gpt::PartitionInfo>,
869    ) -> Result<(), zx::Status> {
870        let mut inner = self.inner.lock().await;
871        if inner.pending_transaction.is_some() {
872            return Err(zx::Status::BAD_STATE);
873        }
874
875        // Sever all connections and clear existing partitions before writing the new partition
876        // table.
877        inner.partitions.clear();
878        inner.composite_partitions.clear();
879        inner.partitions_dir.clear().await;
880
881        // If in-flight I/O on any cleared partition was cancelled, the shared block client's FIFO
882        // will have been terminated to prevent DMA memory corruption. Re-establish a fresh block
883        // client so we can commit the new partition table metadata and share it with the new
884        // partitions.
885        let client =
886            Arc::new(RemoteBlockClient::new(self.block_proxy.clone()).await.map_err(|e| {
887                log::error!(e:?; "Failed to re-establish block client");
888                zx::Status::IO
889            })?);
890        inner.gpt.set_client(client);
891
892        log::info!("Resetting gpt.  Expect data loss!!!");
893        let mut transaction = inner.gpt.create_transaction().unwrap();
894        transaction.partitions = partitions;
895        inner.gpt.commit_transaction(transaction).await?;
896
897        if let Err(error) = inner.bind_all_partitions(&self).await {
898            log::error!(error:?; "Failed to rebind partitions");
899            return Err(zx::Status::BAD_STATE);
900        }
901        log::info!("Rebinding partitions OK!");
902        Ok(())
903    }
904
905    pub async fn shutdown(self: Arc<Self>) {
906        log::info!("Shutting down gpt");
907        let mut inner = self.inner.lock().await;
908        inner.partitions_dir.clear().await;
909        inner.partitions.clear();
910        inner.composite_partitions.clear();
911        if let Some(state) = self.mapper_session.get() {
912            let _ = state.session_proxy.close().await;
913        }
914        self.shutdown.store(true, Ordering::Relaxed);
915        log::info!("Shutting down gpt OK");
916    }
917}
918
919impl Drop for GptManager {
920    fn drop(&mut self) {
921        assert!(self.shutdown.load(Ordering::Relaxed), "Did you forget to shutdown?");
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::GptManager;
928    use block_client::{
929        BlockClient as _, BlockDeviceFlag, BufferSlice, MutableBufferSlice, RemoteBlockClient,
930        WriteFlags,
931    };
932    use block_server::{BlockInfo, DeviceInfo, OffsetMap, WriteOptions};
933    use fidl_fuchsia_io as fio;
934    use fidl_fuchsia_storage_block as fblock;
935    use fidl_fuchsia_storage_partitions as fpartitions;
936    use fuchsia_async as fasync;
937    use fuchsia_component::client::connect_to_named_protocol_at_dir_root;
938    use futures::StreamExt as _;
939    use gpt::{Gpt, Guid, PartitionInfo};
940    use std::num::NonZero;
941    use std::sync::Arc;
942    use std::sync::atomic::{AtomicBool, Ordering};
943    use test_vmo_backed_block_server::{
944        InitialContents, Observer, VmoBackedServer, VmoBackedServerOptions, WriteAction,
945    };
946
947    async fn setup(
948        block_size: u32,
949        block_count: u64,
950        partitions: Vec<PartitionInfo>,
951    ) -> (Arc<VmoBackedServer>, Arc<vfs::directory::immutable::Simple>) {
952        setup_with_options(
953            VmoBackedServerOptions {
954                initial_contents: InitialContents::FromCapacity(block_count),
955                block_size,
956                ..Default::default()
957            },
958            partitions,
959        )
960        .await
961    }
962
963    async fn setup_with_options(
964        opts: VmoBackedServerOptions<'_>,
965        partitions: Vec<PartitionInfo>,
966    ) -> (Arc<VmoBackedServer>, Arc<vfs::directory::immutable::Simple>) {
967        let server = Arc::new(opts.build().unwrap());
968        {
969            let (block_client, block_server) =
970                fidl::endpoints::create_proxy::<fblock::BlockMarker>();
971            let volume_stream = fidl::endpoints::ServerEnd::<fblock::BlockMarker>::from(
972                block_server.into_channel(),
973            )
974            .into_stream();
975            let server_clone = server.clone();
976            let _task = fasync::Task::spawn(async move { server_clone.serve(volume_stream).await });
977            let client = Arc::new(RemoteBlockClient::new(block_client).await.unwrap());
978            Gpt::format(client, partitions).await.unwrap();
979        }
980        (server, vfs::directory::immutable::simple())
981    }
982
983    #[fuchsia::test]
984    async fn load_unformatted_gpt() {
985        let server =
986            Arc::new(VmoBackedServer::new(8, 512, &[]).expect("Failed to create VmoBackedServer"));
987
988        GptManager::new(server.connect(), vfs::directory::immutable::simple())
989            .await
990            .expect_err("load should fail");
991    }
992
993    #[fuchsia::test]
994    async fn load_formatted_empty_gpt() {
995        let (block_device, partitions_dir) = setup(512, 8, vec![]).await;
996
997        let runner = GptManager::new(block_device.connect(), partitions_dir)
998            .await
999            .expect("load should succeed");
1000        runner.shutdown().await;
1001    }
1002
1003    #[fuchsia::test]
1004    async fn load_formatted_gpt_with_one_partition() {
1005        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1006        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1007        const PART_NAME: &str = "part";
1008
1009        let (block_device, partitions_dir) = setup(
1010            512,
1011            8,
1012            vec![PartitionInfo {
1013                label: PART_NAME.to_string(),
1014                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1015                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1016                start_block: 4,
1017                num_blocks: 1,
1018                flags: 0,
1019            }],
1020        )
1021        .await;
1022
1023        let partitions_dir_clone = partitions_dir.clone();
1024        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1025            .await
1026            .expect("load should succeed");
1027        partitions_dir.get_entry("part-000").expect("No entry found");
1028        partitions_dir.get_entry("part-001").map(|_| ()).expect_err("Extra entry found");
1029        runner.shutdown().await;
1030    }
1031
1032    #[fuchsia::test]
1033    async fn load_formatted_gpt_with_two_partitions() {
1034        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1035        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1036        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1037        const PART_1_NAME: &str = "part1";
1038        const PART_2_NAME: &str = "part2";
1039
1040        let (block_device, partitions_dir) = setup(
1041            512,
1042            8,
1043            vec![
1044                PartitionInfo {
1045                    label: PART_1_NAME.to_string(),
1046                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1047                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1048                    start_block: 4,
1049                    num_blocks: 1,
1050                    flags: 0,
1051                },
1052                PartitionInfo {
1053                    label: PART_2_NAME.to_string(),
1054                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1055                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
1056                    start_block: 5,
1057                    num_blocks: 1,
1058                    flags: 0,
1059                },
1060            ],
1061        )
1062        .await;
1063
1064        let partitions_dir_clone = partitions_dir.clone();
1065        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1066            .await
1067            .expect("load should succeed");
1068        partitions_dir.get_entry("part-000").expect("No entry found");
1069        partitions_dir.get_entry("part-001").expect("No entry found");
1070        partitions_dir.get_entry("part-002").map(|_| ()).expect_err("Extra entry found");
1071        runner.shutdown().await;
1072    }
1073
1074    #[fuchsia::test]
1075    async fn partition_io() {
1076        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1077        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1078        const PART_NAME: &str = "part";
1079
1080        let (block_device, partitions_dir) = setup(
1081            512,
1082            8,
1083            vec![PartitionInfo {
1084                label: PART_NAME.to_string(),
1085                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1086                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1087                start_block: 4,
1088                num_blocks: 2,
1089                flags: 0,
1090            }],
1091        )
1092        .await;
1093
1094        let partitions_dir_clone = partitions_dir.clone();
1095        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1096            .await
1097            .expect("load should succeed");
1098
1099        let proxy = vfs::serve_directory(
1100            partitions_dir.clone(),
1101            vfs::path::Path::validate_and_split("part-000").unwrap(),
1102            vfs::execution_scope::ExecutionScope::new(),
1103            fio::PERM_READABLE,
1104        );
1105        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1106            .expect("Failed to open block service");
1107        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1108
1109        assert_eq!(client.block_count(), 2);
1110        assert_eq!(client.block_size(), 512);
1111
1112        let buf = vec![0xabu8; 512];
1113        client.write_at(BufferSlice::Memory(&buf[..]), 0).await.expect("write_at failed");
1114        client
1115            .write_at(BufferSlice::Memory(&buf[..]), 1024)
1116            .await
1117            .expect_err("write_at should fail when writing past partition end");
1118        let mut buf2 = vec![0u8; 512];
1119        client.read_at(MutableBufferSlice::Memory(&mut buf2[..]), 0).await.expect("read_at failed");
1120        assert_eq!(buf, buf2);
1121        client
1122            .read_at(MutableBufferSlice::Memory(&mut buf2[..]), 1024)
1123            .await
1124            .expect_err("read_at should fail when reading past partition end");
1125        client.trim(512..1024).await.expect("trim failed");
1126        client.trim(1..512).await.expect_err("trim with invalid range should fail");
1127        client.trim(1024..1536).await.expect_err("trim past end of partition should fail");
1128        runner.shutdown().await;
1129
1130        // Ensure writes persisted to the partition.
1131        let mut buf = vec![0u8; 512];
1132        let client =
1133            RemoteBlockClient::new(block_device.connect::<fblock::BlockProxy>()).await.unwrap();
1134        client.read_at(MutableBufferSlice::Memory(&mut buf[..]), 2048).await.unwrap();
1135        assert_eq!(&buf[..], &[0xabu8; 512]);
1136    }
1137
1138    #[fuchsia::test]
1139    async fn load_formatted_gpt_with_invalid_primary_header() {
1140        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1141        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1142        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1143        const PART_1_NAME: &str = "part1";
1144        const PART_2_NAME: &str = "part2";
1145
1146        let (block_device, partitions_dir) = setup(
1147            512,
1148            8,
1149            vec![
1150                PartitionInfo {
1151                    label: PART_1_NAME.to_string(),
1152                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1153                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1154                    start_block: 4,
1155                    num_blocks: 1,
1156                    flags: 0,
1157                },
1158                PartitionInfo {
1159                    label: PART_2_NAME.to_string(),
1160                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1161                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
1162                    start_block: 5,
1163                    num_blocks: 1,
1164                    flags: 0,
1165                },
1166            ],
1167        )
1168        .await;
1169        {
1170            let (client, stream) =
1171                fidl::endpoints::create_proxy_and_stream::<fblock::BlockMarker>();
1172            let server = block_device.clone();
1173            let _task = fasync::Task::spawn(async move { server.serve(stream).await });
1174            let client = RemoteBlockClient::new(client).await.unwrap();
1175            client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 512).await.unwrap();
1176        }
1177
1178        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1179            .await
1180            .expect("load should succeed");
1181        partitions_dir.get_entry("part-000").expect("No entry found");
1182        partitions_dir.get_entry("part-001").expect("No entry found");
1183        runner.shutdown().await;
1184    }
1185
1186    #[fuchsia::test]
1187    async fn load_formatted_gpt_with_invalid_primary_partition_table() {
1188        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1189        const PART_INSTANCE_1_GUID: [u8; 16] = [2u8; 16];
1190        const PART_INSTANCE_2_GUID: [u8; 16] = [3u8; 16];
1191        const PART_1_NAME: &str = "part1";
1192        const PART_2_NAME: &str = "part2";
1193
1194        let (block_device, partitions_dir) = setup(
1195            512,
1196            8,
1197            vec![
1198                PartitionInfo {
1199                    label: PART_1_NAME.to_string(),
1200                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1201                    instance_guid: Guid::from_bytes(PART_INSTANCE_1_GUID),
1202                    start_block: 4,
1203                    num_blocks: 1,
1204                    flags: 0,
1205                },
1206                PartitionInfo {
1207                    label: PART_2_NAME.to_string(),
1208                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1209                    instance_guid: Guid::from_bytes(PART_INSTANCE_2_GUID),
1210                    start_block: 5,
1211                    num_blocks: 1,
1212                    flags: 0,
1213                },
1214            ],
1215        )
1216        .await;
1217        {
1218            let (client, stream) =
1219                fidl::endpoints::create_proxy_and_stream::<fblock::BlockMarker>();
1220            let server = block_device.clone();
1221            let _task = fasync::Task::spawn(async move { server.serve(stream).await });
1222            let client = RemoteBlockClient::new(client).await.unwrap();
1223            client.write_at(BufferSlice::Memory(&[0xffu8; 512]), 1024).await.unwrap();
1224        }
1225
1226        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1227            .await
1228            .expect("load should succeed");
1229        partitions_dir.get_entry("part-000").expect("No entry found");
1230        partitions_dir.get_entry("part-001").expect("No entry found");
1231        runner.shutdown().await;
1232    }
1233
1234    #[fuchsia::test]
1235    async fn force_access_passed_through() {
1236        const BLOCK_SIZE: u32 = 512;
1237        const BLOCK_COUNT: u64 = 1024;
1238
1239        struct ForceAccessObserver(Arc<AtomicBool>);
1240
1241        impl Observer for ForceAccessObserver {
1242            fn write(
1243                &self,
1244                _device_block_offset: u64,
1245                _block_count: u32,
1246                _vmo: &Arc<zx::Vmo>,
1247                _vmo_offset: u64,
1248                opts: WriteOptions,
1249            ) -> WriteAction {
1250                assert_eq!(
1251                    opts.flags.contains(WriteFlags::FORCE_ACCESS),
1252                    self.0.load(Ordering::Relaxed)
1253                );
1254                WriteAction::Write
1255            }
1256        }
1257
1258        let expect_force_access = Arc::new(AtomicBool::new(false));
1259        let (server, partitions_dir) = setup_with_options(
1260            VmoBackedServerOptions {
1261                initial_contents: InitialContents::FromCapacity(BLOCK_COUNT),
1262                block_size: BLOCK_SIZE,
1263                observer: Some(Box::new(ForceAccessObserver(expect_force_access.clone()))),
1264                info: DeviceInfo::Block(BlockInfo {
1265                    device_flags: fblock::DeviceFlag::FUA_SUPPORT,
1266                    ..Default::default()
1267                }),
1268                ..Default::default()
1269            },
1270            vec![PartitionInfo {
1271                label: "foo".to_string(),
1272                type_guid: Guid::from_bytes([1; 16]),
1273                instance_guid: Guid::from_bytes([2; 16]),
1274                start_block: 4,
1275                num_blocks: 1,
1276                flags: 0,
1277            }],
1278        )
1279        .await;
1280
1281        let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
1282
1283        let proxy = vfs::serve_directory(
1284            partitions_dir.clone(),
1285            vfs::path::Path::validate_and_split("part-000").unwrap(),
1286            vfs::execution_scope::ExecutionScope::new(),
1287            fio::PERM_READABLE,
1288        );
1289        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1290            .expect("Failed to open block service");
1291        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1292
1293        let buffer = vec![0; BLOCK_SIZE as usize];
1294        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1295
1296        expect_force_access.store(true, Ordering::Relaxed);
1297
1298        client
1299            .write_at_with_opts(
1300                BufferSlice::Memory(&buffer),
1301                0,
1302                WriteOptions { flags: WriteFlags::FORCE_ACCESS, ..Default::default() },
1303            )
1304            .await
1305            .unwrap();
1306
1307        manager.shutdown().await;
1308    }
1309
1310    #[fuchsia::test]
1311    async fn barrier_passed_through() {
1312        const BLOCK_SIZE: u32 = 512;
1313        const BLOCK_COUNT: u64 = 1024;
1314
1315        struct BarrierObserver(Arc<AtomicBool>);
1316
1317        impl Observer for BarrierObserver {
1318            fn write(
1319                &self,
1320                _device_block_offset: u64,
1321                _block_count: u32,
1322                _vmo: &Arc<zx::Vmo>,
1323                _vmo_offset: u64,
1324                opts: WriteOptions,
1325            ) -> WriteAction {
1326                assert_eq!(
1327                    opts.flags.contains(WriteFlags::PRE_BARRIER),
1328                    self.0.load(Ordering::Relaxed)
1329                );
1330                WriteAction::Write
1331            }
1332        }
1333
1334        let expect_barrier = Arc::new(AtomicBool::new(false));
1335        let (server, partitions_dir) = setup_with_options(
1336            VmoBackedServerOptions {
1337                initial_contents: InitialContents::FromCapacity(BLOCK_COUNT),
1338                block_size: BLOCK_SIZE,
1339                observer: Some(Box::new(BarrierObserver(expect_barrier.clone()))),
1340                info: DeviceInfo::Block(BlockInfo {
1341                    device_flags: fblock::DeviceFlag::BARRIER_SUPPORT,
1342                    ..Default::default()
1343                }),
1344                ..Default::default()
1345            },
1346            vec![PartitionInfo {
1347                label: "foo".to_string(),
1348                type_guid: Guid::from_bytes([1; 16]),
1349                instance_guid: Guid::from_bytes([2; 16]),
1350                start_block: 4,
1351                num_blocks: 1,
1352                flags: 0,
1353            }],
1354        )
1355        .await;
1356
1357        let manager = GptManager::new(server.connect(), partitions_dir.clone()).await.unwrap();
1358
1359        let proxy = vfs::serve_directory(
1360            partitions_dir.clone(),
1361            vfs::path::Path::validate_and_split("part-000").unwrap(),
1362            vfs::execution_scope::ExecutionScope::new(),
1363            fio::PERM_READABLE,
1364        );
1365        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1366            .expect("Failed to open block service");
1367        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
1368
1369        let buffer = vec![0; BLOCK_SIZE as usize];
1370        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1371
1372        expect_barrier.store(true, Ordering::Relaxed);
1373        client
1374            .write_at_with_opts(
1375                BufferSlice::Memory(&buffer),
1376                0,
1377                WriteOptions { flags: WriteFlags::PRE_BARRIER, ..Default::default() },
1378            )
1379            .await
1380            .unwrap();
1381
1382        manager.shutdown().await;
1383    }
1384
1385    #[fuchsia::test]
1386    async fn commit_transaction() {
1387        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1388        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1389        const PART_1_NAME: &str = "part";
1390        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1391        const PART_2_NAME: &str = "part2";
1392
1393        let (block_device, partitions_dir) = setup(
1394            512,
1395            16,
1396            vec![
1397                PartitionInfo {
1398                    label: PART_1_NAME.to_string(),
1399                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1400                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1401                    start_block: 4,
1402                    num_blocks: 1,
1403                    flags: 0,
1404                },
1405                PartitionInfo {
1406                    label: PART_2_NAME.to_string(),
1407                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1408                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1409                    start_block: 5,
1410                    num_blocks: 1,
1411                    flags: 0,
1412                },
1413            ],
1414        )
1415        .await;
1416        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1417            .await
1418            .expect("load should succeed");
1419
1420        let part_0_dir = vfs::serve_directory(
1421            partitions_dir.clone(),
1422            vfs::Path::validate_and_split("part-000").unwrap(),
1423            vfs::execution_scope::ExecutionScope::new(),
1424            fio::PERM_READABLE,
1425        );
1426        let part_1_dir = vfs::serve_directory(
1427            partitions_dir.clone(),
1428            vfs::Path::validate_and_split("part-001").unwrap(),
1429            vfs::execution_scope::ExecutionScope::new(),
1430            fio::PERM_READABLE,
1431        );
1432        let part_0_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1433            &part_0_dir,
1434            "partition",
1435        )
1436        .expect("Failed to open Partition service");
1437        let part_1_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1438            &part_1_dir,
1439            "partition",
1440        )
1441        .expect("Failed to open Partition service");
1442
1443        let transaction = runner.create_transaction().await.expect("Failed to create transaction");
1444        part_0_proxy
1445            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1446                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1447                type_guid: Some(fblock::Guid { value: [0xffu8; 16] }),
1448                ..Default::default()
1449            })
1450            .await
1451            .expect("FIDL error")
1452            .expect("Failed to update_metadata");
1453        part_1_proxy
1454            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1455                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1456                flags: Some(1234),
1457                ..Default::default()
1458            })
1459            .await
1460            .expect("FIDL error")
1461            .expect("Failed to update_metadata");
1462        runner.commit_transaction(transaction).await.expect("Failed to commit transaction");
1463
1464        // Ensure the changes have propagated to the correct partitions.
1465        let part_0_block =
1466            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
1467                .expect("Failed to open Volume service");
1468        let (status, guid) = part_0_block.get_type_guid().await.expect("FIDL error");
1469        assert_eq!(status, zx::sys::ZX_OK);
1470        assert_eq!(guid.unwrap().value, [0xffu8; 16]);
1471        let part_1_block =
1472            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_1_dir, "volume")
1473                .expect("Failed to open Volume service");
1474        let metadata =
1475            part_1_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1476        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1477        assert_eq!(metadata.flags, Some(1234));
1478
1479        runner.shutdown().await;
1480    }
1481
1482    #[fuchsia::test]
1483    async fn commit_transaction_with_io_error() {
1484        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1485        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1486        const PART_1_NAME: &str = "part";
1487        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1488        const PART_2_NAME: &str = "part2";
1489
1490        #[derive(Clone)]
1491        struct TransactionObserver(Arc<AtomicBool>);
1492        impl Observer for TransactionObserver {
1493            fn write(
1494                &self,
1495                _device_block_offset: u64,
1496                _block_count: u32,
1497                _vmo: &Arc<zx::Vmo>,
1498                _vmo_offset: u64,
1499                _opts: WriteOptions,
1500            ) -> WriteAction {
1501                if self.0.load(Ordering::Relaxed) { WriteAction::Fail } else { WriteAction::Write }
1502            }
1503        }
1504        let observer = TransactionObserver(Arc::new(AtomicBool::new(false)));
1505        let (block_device, partitions_dir) = setup_with_options(
1506            VmoBackedServerOptions {
1507                initial_contents: InitialContents::FromCapacity(16),
1508                block_size: 512,
1509                observer: Some(Box::new(observer.clone())),
1510                ..Default::default()
1511            },
1512            vec![
1513                PartitionInfo {
1514                    label: PART_1_NAME.to_string(),
1515                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1516                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1517                    start_block: 4,
1518                    num_blocks: 1,
1519                    flags: 0,
1520                },
1521                PartitionInfo {
1522                    label: PART_2_NAME.to_string(),
1523                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1524                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1525                    start_block: 5,
1526                    num_blocks: 1,
1527                    flags: 0,
1528                },
1529            ],
1530        )
1531        .await;
1532        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1533            .await
1534            .expect("load should succeed");
1535
1536        let part_0_dir = vfs::serve_directory(
1537            partitions_dir.clone(),
1538            vfs::Path::validate_and_split("part-000").unwrap(),
1539            vfs::execution_scope::ExecutionScope::new(),
1540            fio::PERM_READABLE,
1541        );
1542        let part_1_dir = vfs::serve_directory(
1543            partitions_dir.clone(),
1544            vfs::Path::validate_and_split("part-001").unwrap(),
1545            vfs::execution_scope::ExecutionScope::new(),
1546            fio::PERM_READABLE,
1547        );
1548        let part_0_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1549            &part_0_dir,
1550            "partition",
1551        )
1552        .expect("Failed to open Partition service");
1553        let part_1_proxy = connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
1554            &part_1_dir,
1555            "partition",
1556        )
1557        .expect("Failed to open Partition service");
1558
1559        let transaction = runner.create_transaction().await.expect("Failed to create transaction");
1560        part_0_proxy
1561            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1562                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1563                type_guid: Some(fblock::Guid { value: [0xffu8; 16] }),
1564                ..Default::default()
1565            })
1566            .await
1567            .expect("FIDL error")
1568            .expect("Failed to update_metadata");
1569        part_1_proxy
1570            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
1571                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1572                flags: Some(1234),
1573                ..Default::default()
1574            })
1575            .await
1576            .expect("FIDL error")
1577            .expect("Failed to update_metadata");
1578
1579        observer.0.store(true, Ordering::Relaxed); // Fail the next write
1580        runner.commit_transaction(transaction).await.expect_err("Commit transaction should fail");
1581
1582        // Ensure the changes did not get applied.
1583        let part_0_block =
1584            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
1585                .expect("Failed to open Volume service");
1586        let (status, guid) = part_0_block.get_type_guid().await.expect("FIDL error");
1587        assert_eq!(status, zx::sys::ZX_OK);
1588        assert_eq!(guid.unwrap().value, [2u8; 16]);
1589        let part_1_block =
1590            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_1_dir, "volume")
1591                .expect("Failed to open Volume service");
1592        let metadata =
1593            part_1_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1594        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1595        assert_eq!(metadata.flags, Some(0));
1596
1597        runner.shutdown().await;
1598    }
1599
1600    #[fuchsia::test]
1601    async fn reset_partition_tables() {
1602        // The test will reset the tables from ["part", "part2"] to
1603        // ["part3", <empty>, "part4", <125 empty entries>].
1604        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1605        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1606        const PART_1_NAME: &str = "part";
1607        const PART_2_INSTANCE_GUID: [u8; 16] = [3u8; 16];
1608        const PART_2_NAME: &str = "part2";
1609        const PART_3_NAME: &str = "part3";
1610        const PART_4_NAME: &str = "part4";
1611
1612        let (block_device, partitions_dir) = setup(
1613            512,
1614            1048576 / 512,
1615            vec![
1616                PartitionInfo {
1617                    label: PART_1_NAME.to_string(),
1618                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1619                    instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
1620                    start_block: 4,
1621                    num_blocks: 1,
1622                    flags: 0,
1623                },
1624                PartitionInfo {
1625                    label: PART_2_NAME.to_string(),
1626                    type_guid: Guid::from_bytes(PART_TYPE_GUID),
1627                    instance_guid: Guid::from_bytes(PART_2_INSTANCE_GUID),
1628                    start_block: 5,
1629                    num_blocks: 1,
1630                    flags: 0,
1631                },
1632            ],
1633        )
1634        .await;
1635        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1636            .await
1637            .expect("load should succeed");
1638        let nil_entry = PartitionInfo {
1639            label: "".to_string(),
1640            type_guid: Guid::from_bytes([0u8; 16]),
1641            instance_guid: Guid::from_bytes([0u8; 16]),
1642            start_block: 0,
1643            num_blocks: 0,
1644            flags: 0,
1645        };
1646        let mut new_partitions = vec![nil_entry; 128];
1647        new_partitions[0] = PartitionInfo {
1648            label: PART_3_NAME.to_string(),
1649            type_guid: Guid::from_bytes(PART_TYPE_GUID),
1650            instance_guid: Guid::from_bytes([1u8; 16]),
1651            start_block: 64,
1652            num_blocks: 2,
1653            flags: 0,
1654        };
1655        new_partitions[2] = PartitionInfo {
1656            label: PART_4_NAME.to_string(),
1657            type_guid: Guid::from_bytes(PART_TYPE_GUID),
1658            instance_guid: Guid::from_bytes([2u8; 16]),
1659            start_block: 66,
1660            num_blocks: 4,
1661            flags: 0,
1662        };
1663        runner.reset_partition_table(new_partitions).await.expect("reset_partition_table failed");
1664        partitions_dir.get_entry("part-000").expect("No entry found");
1665        partitions_dir.get_entry("part-001").map(|_| ()).expect_err("Extra entry found");
1666        partitions_dir.get_entry("part-002").expect("No entry found");
1667
1668        let proxy = vfs::serve_directory(
1669            partitions_dir.clone(),
1670            vfs::path::Path::validate_and_split("part-000").unwrap(),
1671            vfs::execution_scope::ExecutionScope::new(),
1672            fio::PERM_READABLE,
1673        );
1674        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1675            .expect("Failed to open block service");
1676        let (status, name) = block.get_name().await.expect("FIDL error");
1677        assert_eq!(status, zx::sys::ZX_OK);
1678        assert_eq!(name.unwrap(), PART_3_NAME);
1679
1680        runner.shutdown().await;
1681    }
1682
1683    #[fuchsia::test]
1684    async fn reset_partition_tables_fails_if_too_many_partitions() {
1685        let (block_device, partitions_dir) = setup(512, 8, vec![]).await;
1686        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1687            .await
1688            .expect("load should succeed");
1689        let nil_entry = PartitionInfo {
1690            label: "".to_string(),
1691            type_guid: Guid::from_bytes([0u8; 16]),
1692            instance_guid: Guid::from_bytes([0u8; 16]),
1693            start_block: 0,
1694            num_blocks: 0,
1695            flags: 0,
1696        };
1697        let new_partitions = vec![nil_entry; 128];
1698        runner
1699            .reset_partition_table(new_partitions)
1700            .await
1701            .expect_err("reset_partition_table should fail");
1702
1703        runner.shutdown().await;
1704    }
1705
1706    #[fuchsia::test]
1707    async fn reset_partition_tables_fails_if_too_large_partitions() {
1708        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1709        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1710            .await
1711            .expect("load should succeed");
1712        let new_partitions = vec![
1713            PartitionInfo {
1714                label: "a".to_string(),
1715                type_guid: Guid::from_bytes([1u8; 16]),
1716                instance_guid: Guid::from_bytes([1u8; 16]),
1717                start_block: 4,
1718                num_blocks: 2,
1719                flags: 0,
1720            },
1721            PartitionInfo {
1722                label: "b".to_string(),
1723                type_guid: Guid::from_bytes([2u8; 16]),
1724                instance_guid: Guid::from_bytes([2u8; 16]),
1725                start_block: 6,
1726                num_blocks: 200,
1727                flags: 0,
1728            },
1729        ];
1730        runner
1731            .reset_partition_table(new_partitions)
1732            .await
1733            .expect_err("reset_partition_table should fail");
1734
1735        runner.shutdown().await;
1736    }
1737
1738    #[fuchsia::test]
1739    async fn reset_partition_tables_fails_if_partition_overlaps_metadata() {
1740        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1741        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1742            .await
1743            .expect("load should succeed");
1744        let new_partitions = vec![PartitionInfo {
1745            label: "a".to_string(),
1746            type_guid: Guid::from_bytes([1u8; 16]),
1747            instance_guid: Guid::from_bytes([1u8; 16]),
1748            start_block: 1,
1749            num_blocks: 2,
1750            flags: 0,
1751        }];
1752        runner
1753            .reset_partition_table(new_partitions)
1754            .await
1755            .expect_err("reset_partition_table should fail");
1756
1757        runner.shutdown().await;
1758    }
1759
1760    #[fuchsia::test]
1761    async fn reset_partition_tables_fails_if_partitions_overlap() {
1762        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
1763        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1764            .await
1765            .expect("load should succeed");
1766        let new_partitions = vec![
1767            PartitionInfo {
1768                label: "a".to_string(),
1769                type_guid: Guid::from_bytes([1u8; 16]),
1770                instance_guid: Guid::from_bytes([1u8; 16]),
1771                start_block: 32,
1772                num_blocks: 2,
1773                flags: 0,
1774            },
1775            PartitionInfo {
1776                label: "b".to_string(),
1777                type_guid: Guid::from_bytes([2u8; 16]),
1778                instance_guid: Guid::from_bytes([2u8; 16]),
1779                start_block: 33,
1780                num_blocks: 1,
1781                flags: 0,
1782            },
1783        ];
1784        runner
1785            .reset_partition_table(new_partitions)
1786            .await
1787            .expect_err("reset_partition_table should fail");
1788
1789        runner.shutdown().await;
1790    }
1791
1792    #[fuchsia::test]
1793    async fn add_partition() {
1794        let (block_device, partitions_dir) = setup(512, 64, vec![PartitionInfo::nil(); 64]).await;
1795        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
1796            .await
1797            .expect("load should succeed");
1798
1799        let transaction = runner.create_transaction().await.expect("Create transaction failed");
1800        let request = fpartitions::PartitionsManagerAddPartitionRequest {
1801            transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
1802            name: Some("a".to_string()),
1803            type_guid: Some(fblock::Guid { value: [1u8; 16] }),
1804            num_blocks: Some(2),
1805            ..Default::default()
1806        };
1807        runner.add_partition(request).await.expect("add_partition failed");
1808        runner.commit_transaction(transaction).await.expect("add_partition failed");
1809
1810        let proxy = vfs::serve_directory(
1811            partitions_dir.clone(),
1812            vfs::path::Path::validate_and_split("part-000").unwrap(),
1813            vfs::execution_scope::ExecutionScope::new(),
1814            fio::PERM_READABLE,
1815        );
1816        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
1817            .expect("Failed to open block service");
1818        let client: RemoteBlockClient =
1819            RemoteBlockClient::new(block).await.expect("Failed to create block client");
1820
1821        assert_eq!(client.block_count(), 2);
1822        assert_eq!(client.block_size(), 512);
1823
1824        runner.shutdown().await;
1825    }
1826
1827    #[fuchsia::test]
1828    async fn partition_info() {
1829        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1830        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1831        const PART_NAME: &str = "part";
1832
1833        let (block_device, partitions_dir) = setup_with_options(
1834            VmoBackedServerOptions {
1835                initial_contents: InitialContents::FromCapacity(16),
1836                block_size: 512,
1837                info: DeviceInfo::Block(BlockInfo {
1838                    max_transfer_blocks: NonZero::new(2),
1839                    device_flags: BlockDeviceFlag::READONLY
1840                        | BlockDeviceFlag::REMOVABLE
1841                        | BlockDeviceFlag::ZSTD_DECOMPRESSION_SUPPORT,
1842                    ..Default::default()
1843                }),
1844                ..Default::default()
1845            },
1846            vec![PartitionInfo {
1847                label: PART_NAME.to_string(),
1848                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1849                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1850                start_block: 4,
1851                num_blocks: 1,
1852                flags: 0xabcd,
1853            }],
1854        )
1855        .await;
1856
1857        let partitions_dir_clone = partitions_dir.clone();
1858        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
1859            .await
1860            .expect("load should succeed");
1861
1862        let part_dir = vfs::serve_directory(
1863            partitions_dir.clone(),
1864            vfs::path::Path::validate_and_split("part-000").unwrap(),
1865            vfs::execution_scope::ExecutionScope::new(),
1866            fio::PERM_READABLE,
1867        );
1868        let part_block =
1869            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1870                .expect("Failed to open Volume service");
1871        let info: fblock::BlockInfo =
1872            part_block.get_info().await.expect("FIDL error").expect("get_info failed");
1873        assert_eq!(info.block_count, 1);
1874        assert_eq!(info.block_size, 512);
1875        assert_eq!(
1876            info.flags,
1877            BlockDeviceFlag::READONLY
1878                | BlockDeviceFlag::REMOVABLE
1879                | BlockDeviceFlag::ZSTD_DECOMPRESSION_SUPPORT
1880        );
1881        assert_eq!(info.max_transfer_size, 1024);
1882
1883        let metadata: fblock::PartitionInfo =
1884            part_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
1885        assert_eq!(metadata.name, Some(PART_NAME.to_string()));
1886        assert_eq!(metadata.type_guid.unwrap().value, PART_TYPE_GUID);
1887        assert_eq!(metadata.instance_guid.unwrap().value, PART_INSTANCE_GUID);
1888        assert_eq!(metadata.start_block_offset, Some(4));
1889        assert_eq!(metadata.num_blocks, Some(1));
1890        assert_eq!(metadata.flags, Some(0xabcd));
1891
1892        runner.shutdown().await;
1893    }
1894
1895    #[fuchsia::test]
1896    async fn nested_gpt() {
1897        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1898        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1899        const PART_NAME: &str = "part";
1900
1901        let vmo = zx::Vmo::create(64 * 512).unwrap();
1902        let vmo_clone = vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap();
1903        let (outer_block_device, outer_partitions_dir) = setup_with_options(
1904            VmoBackedServerOptions {
1905                initial_contents: InitialContents::FromVmo(vmo_clone),
1906                block_size: 512,
1907                info: DeviceInfo::Block(BlockInfo {
1908                    device_flags: BlockDeviceFlag::READONLY | BlockDeviceFlag::REMOVABLE,
1909                    ..Default::default()
1910                }),
1911                ..Default::default()
1912            },
1913            vec![PartitionInfo {
1914                label: PART_NAME.to_string(),
1915                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1916                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1917                start_block: 4,
1918                num_blocks: 16,
1919                flags: 0xabcd,
1920            }],
1921        )
1922        .await;
1923
1924        let outer_partitions_dir_clone = outer_partitions_dir.clone();
1925        let outer_runner =
1926            GptManager::new(outer_block_device.connect(), outer_partitions_dir_clone)
1927                .await
1928                .expect("load should succeed");
1929
1930        let outer_part_dir = vfs::serve_directory(
1931            outer_partitions_dir.clone(),
1932            vfs::path::Path::validate_and_split("part-000").unwrap(),
1933            vfs::execution_scope::ExecutionScope::new(),
1934            fio::PERM_READABLE,
1935        );
1936        let part_block =
1937            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&outer_part_dir, "volume")
1938                .expect("Failed to open Block service");
1939
1940        let client = Arc::new(RemoteBlockClient::new(part_block.clone()).await.unwrap());
1941        let _ = gpt::Gpt::format(
1942            client,
1943            vec![PartitionInfo {
1944                label: PART_NAME.to_string(),
1945                type_guid: Guid::from_bytes(PART_TYPE_GUID),
1946                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
1947                start_block: 5,
1948                num_blocks: 1,
1949                flags: 0xabcd,
1950            }],
1951        )
1952        .await
1953        .unwrap();
1954
1955        let partitions_dir = vfs::directory::immutable::simple();
1956        let partitions_dir_clone = partitions_dir.clone();
1957        let runner =
1958            GptManager::new(part_block, partitions_dir_clone).await.expect("load should succeed");
1959        let part_dir = vfs::serve_directory(
1960            partitions_dir.clone(),
1961            vfs::path::Path::validate_and_split("part-000").unwrap(),
1962            vfs::execution_scope::ExecutionScope::new(),
1963            fio::PERM_READABLE,
1964        );
1965        let inner_part_block =
1966            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
1967                .expect("Failed to open Block service");
1968
1969        let client =
1970            RemoteBlockClient::new(inner_part_block).await.expect("Failed to create block client");
1971        assert_eq!(client.block_count(), 1);
1972        assert_eq!(client.block_size(), 512);
1973
1974        let buffer = vec![0xaa; 512];
1975        client.write_at(BufferSlice::Memory(&buffer), 0).await.unwrap();
1976        client
1977            .write_at(BufferSlice::Memory(&buffer), 512)
1978            .await
1979            .expect_err("Write past end should fail");
1980        client.flush().await.unwrap();
1981
1982        runner.shutdown().await;
1983        outer_runner.shutdown().await;
1984
1985        // Check that the write targeted the correct block (4 + 5 = 9)
1986        let data = vmo.read_to_vec::<u8>(9 * 512, 512).unwrap();
1987        assert_eq!(&data[..], &buffer[..]);
1988    }
1989
1990    #[fuchsia::test]
1991    async fn open_session_with_options_is_rejected() {
1992        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
1993        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
1994        const PART_NAME: &str = "foo";
1995
1996        let (block_device, partitions_dir) = setup_with_options(
1997            VmoBackedServerOptions {
1998                initial_contents: InitialContents::FromCapacity(16),
1999                block_size: 512,
2000                ..Default::default()
2001            },
2002            vec![PartitionInfo {
2003                label: PART_NAME.to_string(),
2004                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2005                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
2006                start_block: 4,
2007                num_blocks: 2,
2008                flags: 0xabcd,
2009            }],
2010        )
2011        .await;
2012
2013        let partitions_dir_clone = partitions_dir.clone();
2014        let runner = GptManager::new(block_device.connect(), partitions_dir_clone)
2015            .await
2016            .expect("load should succeed");
2017
2018        let part_dir = vfs::serve_directory(
2019            partitions_dir.clone(),
2020            vfs::path::Path::validate_and_split("part-000").unwrap(),
2021            vfs::execution_scope::ExecutionScope::new(),
2022            fio::PERM_READABLE,
2023        );
2024
2025        let part_block =
2026            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
2027                .expect("Failed to open Block service");
2028
2029        // Attempting to open a session with a valid offset map should fail.
2030        let (session, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
2031        part_block
2032            .open_session_with_options(
2033                server_end,
2034                &[fblock::BlockOffsetMapping { target_block_offset: 1, length: 2 }],
2035            )
2036            .expect("FIDL error");
2037        session
2038            .get_fifo()
2039            .await
2040            .expect_err("Session should be closed because nested mappings are not supported");
2041
2042        runner.shutdown().await;
2043    }
2044
2045    #[fuchsia::test]
2046    async fn test_open_session_with_options_rejects_nested_mappings() {
2047        let (block_device, partitions_dir) = setup(
2048            512,
2049            2048,
2050            vec![
2051                PartitionInfo {
2052                    label: "super".to_string(),
2053                    type_guid: Guid::from_bytes([1; 16]),
2054                    instance_guid: Guid::from_bytes([2; 16]),
2055                    start_block: 34,
2056                    num_blocks: 10,
2057                    flags: 0,
2058                },
2059                PartitionInfo {
2060                    label: "userdata".to_string(),
2061                    type_guid: Guid::from_bytes([1; 16]),
2062                    instance_guid: Guid::from_bytes([3; 16]),
2063                    start_block: 50,
2064                    num_blocks: 10,
2065                    flags: 0,
2066                },
2067            ],
2068        )
2069        .await;
2070
2071        let partitions_dir_clone = partitions_dir.clone();
2072        let runner = GptManager::new_with_config(
2073            block_device.connect(),
2074            partitions_dir_clone,
2075            crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
2076        )
2077        .await
2078        .expect("load should succeed");
2079
2080        let part_dir = vfs::serve_directory(
2081            partitions_dir.clone(),
2082            vfs::path::Path::validate_and_split("part-000").unwrap(),
2083            vfs::execution_scope::ExecutionScope::new(),
2084            fio::PERM_READABLE,
2085        );
2086
2087        let part_block =
2088            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
2089                .expect("Failed to open Block service");
2090
2091        let metadata: fblock::PartitionInfo =
2092            part_block.get_metadata().await.expect("FIDL error").expect("get_metadata failed");
2093        assert_eq!(metadata.name, Some("super_and_userdata".to_string()));
2094        assert!(metadata.start_block_offset.is_none());
2095        assert!(metadata.flags.is_none());
2096
2097        // Attempting to open a session with an offset map on a merged GPT partition should fail
2098        // because it has static mappings, and nested mappings are not supported.
2099        let (session, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
2100        part_block
2101            .open_session_with_options(
2102                server_end,
2103                &[fblock::BlockOffsetMapping { target_block_offset: 0, length: 3 }],
2104            )
2105            .expect("FIDL error");
2106        session.get_fifo().await.expect_err("Session should be closed due to nested mapping");
2107
2108        {
2109            let inner = runner.inner.lock().await;
2110            let backend = inner.composite_partitions.get(&0).unwrap().session_manager().interface();
2111            assert!(backend.passthrough());
2112        }
2113
2114        runner.shutdown().await;
2115    }
2116
2117    #[fuchsia::test]
2118    async fn test_vmos_detached_on_session_close() {
2119        let (block_device, partitions_dir) = setup(
2120            512,
2121            100,
2122            vec![PartitionInfo {
2123                type_guid: Guid::from_bytes([2u8; 16]),
2124                instance_guid: Guid::from_bytes([2u8; 16]),
2125                start_block: 34,
2126                num_blocks: 10,
2127                flags: 0,
2128                label: "test".to_string(),
2129            }],
2130        )
2131        .await;
2132
2133        let runner = GptManager::new(block_device.connect(), partitions_dir.clone()).await.unwrap();
2134        let proxy = vfs::serve_directory(
2135            partitions_dir.clone(),
2136            vfs::path::Path::validate_and_split("part-000").unwrap(),
2137            vfs::execution_scope::ExecutionScope::new(),
2138            fio::PERM_READABLE,
2139        );
2140        let block = connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&proxy, "volume")
2141            .expect("Failed to open block service");
2142        let client = RemoteBlockClient::new(block).await.expect("Failed to create block client");
2143
2144        {
2145            let inner = runner.inner.lock().await;
2146            let backend = inner.partitions.get(&0).unwrap().session_manager().interface();
2147            assert_eq!(backend.vmo_count(), 1);
2148        }
2149
2150        client.close().await.expect("Failed to close client");
2151
2152        {
2153            let inner = runner.inner.lock().await;
2154            let backend = inner.partitions.get(&0).unwrap().session_manager().interface();
2155            assert_eq!(backend.vmo_count(), 0);
2156        }
2157
2158        runner.shutdown().await;
2159    }
2160
2161    #[test]
2162    fn test_should_passthrough_partition() {
2163        use super::{ALL_SYSTEM_PARTITION_LABELS, should_passthrough_partition};
2164
2165        let system_label = ALL_SYSTEM_PARTITION_LABELS[0].to_string();
2166
2167        // Single mapping on a system label partition -> should passthrough.
2168        let single_config = block_server::PartitionInfo {
2169            name: system_label.clone(),
2170            type_guid: [1; 16],
2171            instance_guid: [2; 16],
2172            flags: Some(0),
2173            start_block_offset: Some(0),
2174            block_count: 100,
2175            ..Default::default()
2176        };
2177        assert!(should_passthrough_partition(&single_config));
2178
2179        // Multiple mappings on the exact same system label partition -> should passthrough.
2180        let multi_config = block_server::PartitionInfo {
2181            name: system_label,
2182            type_guid: [1; 16],
2183            instance_guid: [2; 16],
2184            flags: Some(0),
2185            start_block_offset: Some(0),
2186            block_count: 200,
2187            ..Default::default()
2188        };
2189        assert!(should_passthrough_partition(&multi_config));
2190    }
2191
2192    #[fuchsia::test]
2193    async fn test_merged_partition_passthrough_behavior() {
2194        // Test Case 1: Discontiguous -> passthrough = false
2195        {
2196            let (block_device, partitions_dir) = setup(
2197                512,
2198                2048,
2199                vec![
2200                    PartitionInfo {
2201                        label: "super".to_string(),
2202                        type_guid: Guid::from_bytes([1; 16]),
2203                        instance_guid: Guid::from_bytes([2; 16]),
2204                        start_block: 34,
2205                        num_blocks: 10,
2206                        flags: 0,
2207                    },
2208                    PartitionInfo {
2209                        label: "userdata".to_string(),
2210                        type_guid: Guid::from_bytes([1; 16]),
2211                        instance_guid: Guid::from_bytes([3; 16]),
2212                        start_block: 50, // Discontiguous (44 != 50)
2213                        num_blocks: 10,
2214                        flags: 0,
2215                    },
2216                ],
2217            )
2218            .await;
2219
2220            let runner = GptManager::new_with_config(
2221                block_device.connect(),
2222                partitions_dir,
2223                crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
2224            )
2225            .await
2226            .expect("load should succeed");
2227
2228            {
2229                let inner = runner.inner.lock().await;
2230                let backend =
2231                    inner.composite_partitions.get(&0).unwrap().session_manager().interface();
2232                assert!(backend.passthrough());
2233            }
2234            runner.shutdown().await;
2235        }
2236
2237        // Test Case 2: Contiguous -> passthrough = true (after coalescing it will be 1 mapping)
2238        {
2239            let (block_device, partitions_dir) = setup(
2240                512,
2241                2048,
2242                vec![
2243                    PartitionInfo {
2244                        label: "super".to_string(),
2245                        type_guid: Guid::from_bytes([1; 16]),
2246                        instance_guid: Guid::from_bytes([2; 16]),
2247                        start_block: 34,
2248                        num_blocks: 10,
2249                        flags: 0,
2250                    },
2251                    PartitionInfo {
2252                        label: "userdata".to_string(),
2253                        type_guid: Guid::from_bytes([1; 16]),
2254                        instance_guid: Guid::from_bytes([3; 16]),
2255                        start_block: 44, // Contiguous (34 + 10 = 44)
2256                        num_blocks: 10,
2257                        flags: 0,
2258                    },
2259                ],
2260            )
2261            .await;
2262
2263            let runner = GptManager::new_with_config(
2264                block_device.connect(),
2265                partitions_dir,
2266                crate::config::Config { merge_super_and_userdata: true, ..Default::default() },
2267            )
2268            .await
2269            .expect("load should succeed");
2270
2271            {
2272                let inner = runner.inner.lock().await;
2273                let backend =
2274                    inner.composite_partitions.get(&0).unwrap().session_manager().interface();
2275                assert!(backend.passthrough());
2276            }
2277            runner.shutdown().await;
2278        }
2279    }
2280
2281    #[fuchsia::test]
2282    async fn reset_partition_table_severs_existing_connections() {
2283        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
2284        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
2285        const PART_1_NAME: &str = "part";
2286
2287        let (block_device, partitions_dir) = setup(
2288            512,
2289            1048576 / 512,
2290            vec![PartitionInfo {
2291                label: PART_1_NAME.to_string(),
2292                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2293                instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
2294                start_block: 4,
2295                num_blocks: 10,
2296                flags: 0,
2297            }],
2298        )
2299        .await;
2300
2301        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
2302            .await
2303            .expect("load should succeed");
2304
2305        let part_0_dir = vfs::serve_directory(
2306            partitions_dir.clone(),
2307            vfs::path::Path::validate_and_split("part-000").unwrap(),
2308            vfs::execution_scope::ExecutionScope::new(),
2309            fio::PERM_READABLE,
2310        );
2311
2312        let part_0_block =
2313            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
2314                .expect("Failed to open Volume service");
2315        let part_0_partition =
2316            connect_to_named_protocol_at_dir_root::<fpartitions::PartitionMarker>(
2317                &part_0_dir,
2318                "partition",
2319            )
2320            .expect("Failed to open Partition service");
2321
2322        let (part_0_block_clone, server_end) =
2323            fidl::endpoints::create_proxy::<fblock::BlockMarker>();
2324        part_0_dir
2325            .open(
2326                "volume",
2327                fio::Flags::PROTOCOL_SERVICE,
2328                &fio::Options::default(),
2329                server_end.into_channel(),
2330            )
2331            .expect("Failed to open volume");
2332
2333        let client =
2334            RemoteBlockClient::new(part_0_block).await.expect("Failed to create block client");
2335
2336        let buf = vec![0xabu8; 512];
2337        client.write_at(BufferSlice::Memory(&buf[..]), 0).await.expect("write_at failed");
2338
2339        let nil_entry = PartitionInfo {
2340            label: "".to_string(),
2341            type_guid: Guid::from_bytes([0u8; 16]),
2342            instance_guid: Guid::from_bytes([0u8; 16]),
2343            start_block: 0,
2344            num_blocks: 0,
2345            flags: 0,
2346        };
2347        let mut new_partitions = vec![nil_entry; 128];
2348        new_partitions[0] = PartitionInfo {
2349            label: "part_new".to_string(),
2350            type_guid: Guid::from_bytes(PART_TYPE_GUID),
2351            instance_guid: Guid::from_bytes([1u8; 16]),
2352            start_block: 64,
2353            num_blocks: 2,
2354            flags: 0,
2355        };
2356
2357        runner.reset_partition_table(new_partitions).await.expect("reset_partition_table failed");
2358
2359        // The old partition connection should be severed.
2360        let transaction = runner.create_transaction().await.expect("Failed to create transaction");
2361        part_0_partition
2362            .update_metadata(fpartitions::PartitionUpdateMetadataRequest {
2363                transaction: Some(transaction),
2364                flags: Some(1234),
2365                ..Default::default()
2366            })
2367            .await
2368            .expect_err(
2369                "update_metadata on stale partition connection should fail with PEER_CLOSED",
2370            );
2371
2372        // The old block connection should be severed (get_name should fail with PEER_CLOSED).
2373        part_0_block_clone
2374            .get_name()
2375            .await
2376            .expect_err("get_name on stale block connection should fail");
2377
2378        // Subsequent writes on the old client should fail because the session/connection was
2379        // severed.
2380        client
2381            .write_at(BufferSlice::Memory(&buf[..]), 0)
2382            .await
2383            .expect_err("write_at on stale client should fail");
2384
2385        runner.shutdown().await;
2386    }
2387
2388    #[fuchsia::test(threads = 2)]
2389    async fn reset_partition_table_with_in_flight_io_succeeds() {
2390        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
2391        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
2392        const PART_1_NAME: &str = "part";
2393
2394        struct PauseObserver {
2395            started_tx: std::sync::mpsc::Sender<()>,
2396            resume_rx: std::sync::Mutex<std::sync::mpsc::Receiver<()>>,
2397        }
2398
2399        impl Observer for PauseObserver {
2400            fn read(
2401                &self,
2402                device_block_offset: u64,
2403                _block_count: u32,
2404                _vmo: &Arc<zx::Vmo>,
2405                _vmo_offset: u64,
2406            ) {
2407                // Only pause partition reads (LBA 4 is the start of the partition).
2408                if device_block_offset >= 4 {
2409                    let _ = self.started_tx.send(());
2410                    let _ = self.resume_rx.lock().unwrap().recv();
2411                }
2412            }
2413        }
2414
2415        let (started_tx, started_rx) = std::sync::mpsc::channel();
2416        let (resume_tx, resume_rx) = std::sync::mpsc::channel();
2417
2418        let (block_device, partitions_dir) = setup_with_options(
2419            VmoBackedServerOptions {
2420                initial_contents: InitialContents::FromCapacity(1048576 / 512),
2421                block_size: 512,
2422                observer: Some(Box::new(PauseObserver {
2423                    started_tx,
2424                    resume_rx: std::sync::Mutex::new(resume_rx),
2425                })),
2426                ..Default::default()
2427            },
2428            vec![PartitionInfo {
2429                label: PART_1_NAME.to_string(),
2430                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2431                instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
2432                start_block: 4,
2433                num_blocks: 10,
2434                flags: 0,
2435            }],
2436        )
2437        .await;
2438
2439        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
2440            .await
2441            .expect("load should succeed");
2442
2443        let part_0_dir = vfs::serve_directory(
2444            partitions_dir.clone(),
2445            vfs::path::Path::validate_and_split("part-000").unwrap(),
2446            vfs::execution_scope::ExecutionScope::new(),
2447            fio::PERM_READABLE,
2448        );
2449
2450        let part_0_block =
2451            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
2452                .expect("Failed to open Volume service");
2453
2454        let client =
2455            RemoteBlockClient::new(part_0_block).await.expect("Failed to create block client");
2456
2457        // Spawn a background read task that will pause in PauseObserver at the disk level.
2458        let mut buf = vec![0u8; 512];
2459        let read_task = fasync::Task::spawn(async move {
2460            client.read_at(MutableBufferSlice::Memory(&mut buf[..]), 0).await
2461        });
2462
2463        // Deterministically wait for the read to arrive at the underlying disk.
2464        started_rx.recv().expect("Failed to receive read start notification");
2465
2466        let nil_entry = PartitionInfo {
2467            label: "".to_string(),
2468            type_guid: Guid::from_bytes([0u8; 16]),
2469            instance_guid: Guid::from_bytes([0u8; 16]),
2470            start_block: 0,
2471            num_blocks: 0,
2472            flags: 0,
2473        };
2474        let mut new_partitions = vec![nil_entry; 128];
2475        new_partitions[0] = PartitionInfo {
2476            label: "part_new".to_string(),
2477            type_guid: Guid::from_bytes(PART_TYPE_GUID),
2478            instance_guid: Guid::from_bytes([1u8; 16]),
2479            start_block: 64,
2480            num_blocks: 2,
2481            flags: 0,
2482        };
2483
2484        // Reset partition table while the read is guaranteed to be in-flight at the disk level.
2485        // Once reset_partition_table severs the partition connections, resume the observer so
2486        // the mock disk unblocks.
2487        let reset_fut = runner.reset_partition_table(new_partitions);
2488        let resume_task = fasync::Task::spawn(async move {
2489            fasync::Timer::new(std::time::Duration::from_millis(50)).await;
2490            let _ = resume_tx.send(());
2491        });
2492
2493        reset_fut.await.expect("reset_partition_table failed");
2494        resume_task.await;
2495
2496        // The in-flight read should fail because its connection was severed by table reset.
2497        read_task.await.expect_err("in-flight read should fail");
2498
2499        runner.shutdown().await;
2500    }
2501
2502    #[fuchsia::test]
2503    async fn test_mapper_passthrough_on_partition() {
2504        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
2505        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
2506        const PART_NAME: &str = "super";
2507
2508        let (block_device, partitions_dir) = setup(
2509            512,
2510            64,
2511            vec![PartitionInfo {
2512                label: PART_NAME.to_string(),
2513                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2514                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
2515                start_block: 8,
2516                num_blocks: 16,
2517                flags: 0,
2518            }],
2519        )
2520        .await;
2521
2522        let mapper_proxy = block_device.connect_mapper();
2523        let partitions_dir_clone = partitions_dir.clone();
2524        let runner = GptManager::new_with_mapper(
2525            block_device.connect(),
2526            Some(mapper_proxy),
2527            partitions_dir_clone,
2528        )
2529        .await
2530        .expect("load should succeed");
2531
2532        let part_dir = vfs::serve_directory(
2533            partitions_dir.clone(),
2534            vfs::path::Path::validate_and_split("part-000").unwrap(),
2535            vfs::execution_scope::ExecutionScope::new(),
2536            fio::PERM_READABLE,
2537        );
2538        let part_mapper =
2539            connect_to_named_protocol_at_dir_root::<fblock::MapperMarker>(&part_dir, "mapper")
2540                .expect("Failed to open Mapper service");
2541
2542        let (_session_proxy, session_server_end) =
2543            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
2544        let mapping_vmo = zx::Vmo::create(mapping::MAPPING_VMO_SIZE).unwrap();
2545        let port = zx::Port::create();
2546        let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE).unwrap();
2547
2548        part_mapper
2549            .open_session(session_server_end, mapping_vmo, Some(port), Some(delivery_queue))
2550            .await
2551            .expect("FIDL open_session failed")
2552            .expect("open_session returned error");
2553
2554        runner.shutdown().await;
2555    }
2556
2557    #[fuchsia::test]
2558    async fn test_mapper_not_supported_without_parent_mapper() {
2559        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
2560        const PART_INSTANCE_GUID: [u8; 16] = [2u8; 16];
2561        const PART_NAME: &str = "super";
2562
2563        let (block_device, partitions_dir) = setup(
2564            512,
2565            64,
2566            vec![PartitionInfo {
2567                label: PART_NAME.to_string(),
2568                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2569                instance_guid: Guid::from_bytes(PART_INSTANCE_GUID),
2570                start_block: 8,
2571                num_blocks: 16,
2572                flags: 0,
2573            }],
2574        )
2575        .await;
2576
2577        let (block_proxy, mut stream) =
2578            fidl::endpoints::create_proxy_and_stream::<fblock::BlockMarker>();
2579        let underlying: fblock::BlockProxy = block_device.connect();
2580        let _block_task = fasync::Task::spawn(async move {
2581            while let Some(Ok(request)) = stream.next().await {
2582                match request {
2583                    fblock::BlockRequest::ConnectMapper { responder, .. } => {
2584                        let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
2585                    }
2586                    fblock::BlockRequest::GetInfo { responder } => {
2587                        let _ = responder
2588                            .send(underlying.get_info().await.unwrap().as_ref().map_err(|s| *s));
2589                    }
2590                    fblock::BlockRequest::OpenSession { session, .. } => {
2591                        let _ = underlying.open_session(session);
2592                    }
2593                    _ => unimplemented!(),
2594                }
2595            }
2596        });
2597
2598        let runner = GptManager::new(block_proxy, partitions_dir.clone())
2599            .await
2600            .expect("load should succeed");
2601
2602        let part_dir = vfs::serve_directory(
2603            partitions_dir.clone(),
2604            vfs::path::Path::validate_and_split("part-000").unwrap(),
2605            vfs::execution_scope::ExecutionScope::new(),
2606            fio::PERM_READABLE,
2607        );
2608        let part_block =
2609            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_dir, "volume")
2610                .expect("Failed to open Block service");
2611        let (part_mapper, mapper_server_end) =
2612            fidl::endpoints::create_proxy::<fblock::MapperMarker>();
2613        part_block
2614            .connect_mapper(mapper_server_end)
2615            .await
2616            .expect("FIDL connect_mapper failed")
2617            .expect("connect_mapper returned error");
2618
2619        let (_session_proxy, session_server_end) =
2620            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
2621        let mapping_vmo = zx::Vmo::create(mapping::MAPPING_VMO_SIZE).unwrap();
2622        let port = zx::Port::create();
2623        let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE).unwrap();
2624
2625        assert_eq!(
2626            part_mapper
2627                .open_session(session_server_end, mapping_vmo, Some(port), Some(delivery_queue))
2628                .await
2629                .expect("FIDL open_session failed"),
2630            Err(zx::Status::NOT_SUPPORTED.into_raw())
2631        );
2632
2633        runner.shutdown().await;
2634    }
2635
2636    #[fuchsia::test]
2637    async fn reset_partition_table_severs_passthrough_connections() {
2638        const PART_TYPE_GUID: [u8; 16] = [2u8; 16];
2639        const PART_1_INSTANCE_GUID: [u8; 16] = [2u8; 16];
2640        const PART_1_NAME: &str = "fvm";
2641
2642        let (block_device, partitions_dir) = setup(
2643            512,
2644            1048576 / 512,
2645            vec![PartitionInfo {
2646                label: PART_1_NAME.to_string(),
2647                type_guid: Guid::from_bytes(PART_TYPE_GUID),
2648                instance_guid: Guid::from_bytes(PART_1_INSTANCE_GUID),
2649                start_block: 4,
2650                num_blocks: 10,
2651                flags: 0,
2652            }],
2653        )
2654        .await;
2655
2656        let runner = GptManager::new(block_device.connect(), partitions_dir.clone())
2657            .await
2658            .expect("load should succeed");
2659
2660        let part_0_dir = vfs::serve_directory(
2661            partitions_dir.clone(),
2662            vfs::path::Path::validate_and_split("part-000").unwrap(),
2663            vfs::execution_scope::ExecutionScope::new(),
2664            fio::PERM_READABLE,
2665        );
2666
2667        let part_0_block =
2668            connect_to_named_protocol_at_dir_root::<fblock::BlockMarker>(&part_0_dir, "volume")
2669                .expect("Failed to open Volume service");
2670
2671        let (part_0_block_clone, server_end) =
2672            fidl::endpoints::create_proxy::<fblock::BlockMarker>();
2673        part_0_dir
2674            .open(
2675                "volume",
2676                fio::Flags::PROTOCOL_SERVICE,
2677                &fio::Options::default(),
2678                server_end.into_channel(),
2679            )
2680            .expect("Failed to open volume");
2681
2682        let client =
2683            RemoteBlockClient::new(part_0_block).await.expect("Failed to create block client");
2684
2685        let buf = vec![0xabu8; 512];
2686        client.write_at(BufferSlice::Memory(&buf[..]), 0).await.expect("write_at failed");
2687
2688        let nil_entry = PartitionInfo {
2689            label: "".to_string(),
2690            type_guid: Guid::from_bytes([0u8; 16]),
2691            instance_guid: Guid::from_bytes([0u8; 16]),
2692            start_block: 0,
2693            num_blocks: 0,
2694            flags: 0,
2695        };
2696        let mut new_partitions = vec![nil_entry; 128];
2697        new_partitions[0] = PartitionInfo {
2698            label: "part_new".to_string(),
2699            type_guid: Guid::from_bytes(PART_TYPE_GUID),
2700            instance_guid: Guid::from_bytes([1u8; 16]),
2701            start_block: 64,
2702            num_blocks: 2,
2703            flags: 0,
2704        };
2705
2706        runner.reset_partition_table(new_partitions).await.expect("reset_partition_table failed");
2707
2708        // The old block connection should be severed (get_name should fail with PEER_CLOSED).
2709        part_0_block_clone
2710            .get_name()
2711            .await
2712            .expect_err("get_name on stale block connection should fail");
2713
2714        // Subsequent writes on the old client should fail because the session/connection was
2715        // severed.
2716        client
2717            .write_at(BufferSlice::Memory(&buf[..]), 0)
2718            .await
2719            .expect_err("write_at on stale client should fail");
2720
2721        runner.shutdown().await;
2722    }
2723
2724    #[fuchsia::test]
2725    async fn test_mapper_passthrough_on_composite_partition() {
2726        let (block_device, partitions_dir) = setup(
2727            512,
2728            64,
2729            vec![
2730                PartitionInfo {
2731                    label: "super".to_string(),
2732                    type_guid: Guid::from_bytes([1u8; 16]),
2733                    instance_guid: Guid::from_bytes([2u8; 16]),
2734                    start_block: 8,
2735                    num_blocks: 8,
2736                    flags: 0,
2737                },
2738                PartitionInfo {
2739                    label: "userdata".to_string(),
2740                    type_guid: Guid::from_bytes([1u8; 16]),
2741                    instance_guid: Guid::from_bytes([3u8; 16]),
2742                    start_block: 16,
2743                    num_blocks: 8,
2744                    flags: 0,
2745                },
2746            ],
2747        )
2748        .await;
2749
2750        let mapper_proxy = block_device.connect_mapper();
2751        let partitions_dir_clone = partitions_dir.clone();
2752        let config = crate::config::Config { merge_super_and_userdata: true, ..Default::default() };
2753        let runner = GptManager::new_with_config_and_mapper(
2754            block_device.connect(),
2755            Some(mapper_proxy),
2756            partitions_dir_clone,
2757            config,
2758        )
2759        .await
2760        .expect("load should succeed");
2761
2762        let part_dir = vfs::serve_directory(
2763            partitions_dir.clone(),
2764            vfs::path::Path::validate_and_split("part-000").unwrap(),
2765            vfs::execution_scope::ExecutionScope::new(),
2766            fio::PERM_READABLE,
2767        );
2768        let part_mapper =
2769            connect_to_named_protocol_at_dir_root::<fblock::MapperMarker>(&part_dir, "mapper")
2770                .expect("Failed to open Mapper service");
2771
2772        let (_session_proxy, session_server_end) =
2773            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
2774        let mapping_vmo = zx::Vmo::create(mapping::MAPPING_VMO_SIZE).unwrap();
2775        let port = zx::Port::create();
2776        let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE).unwrap();
2777
2778        part_mapper
2779            .open_session(session_server_end, mapping_vmo, Some(port), Some(delivery_queue))
2780            .await
2781            .expect("FIDL open_session failed")
2782            .expect("open_session returned error");
2783
2784        runner.shutdown().await;
2785    }
2786
2787    #[fuchsia::test]
2788    async fn test_register_mappings_payload_preserved() {
2789        let (block_device, partitions_dir) = setup(512, 64, vec![]).await;
2790
2791        let (mapper_proxy, mut mapper_stream) =
2792            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
2793
2794        let (commands_tx, mut commands_rx) = futures::channel::mpsc::unbounded();
2795        let _mapper_task = fasync::Task::spawn(async move {
2796            if let Some(Ok(fblock::MapperRequest::OpenSession { mapping_vmo, responder, .. })) =
2797                mapper_stream.next().await
2798            {
2799                let vmo_dup = mapping_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
2800                let _receiver_thread = std::thread::spawn(move || {
2801                    let mut receiver = vmo_fifo::Receiver::<mapping::RawMappingCommand>::new(
2802                        vmo_dup,
2803                        mapping::PENDING_COMMANDS_CAPACITY,
2804                    )
2805                    .unwrap();
2806                    while let Ok(msg) = receiver.peek() {
2807                        let cmd = *msg;
2808                        let payload_len = cmd.extent_count as u32 * 8;
2809                        let payload_slice = msg.payload_slice(cmd.offset, payload_len);
2810                        let mut payload = vec![0u8; payload_len as usize];
2811                        payload_slice.copy_to_slice(&mut payload);
2812                        commands_tx.unbounded_send((cmd, payload)).unwrap();
2813                        let _ = msg.pop();
2814                    }
2815                });
2816                responder.send(Ok(())).unwrap();
2817            }
2818        });
2819
2820        let runner =
2821            GptManager::new_with_mapper(block_device.connect(), Some(mapper_proxy), partitions_dir)
2822                .await
2823                .expect("load should succeed");
2824
2825        let offset_map1 = block_server::OffsetMap::new(vec![block_server::BlockOffsetMapping {
2826            target_block_offset: 0,
2827            length: 8,
2828        }])
2829        .unwrap();
2830        let offset_map2 = block_server::OffsetMap::new(vec![block_server::BlockOffsetMapping {
2831            target_block_offset: 8,
2832            length: 8,
2833        }])
2834        .unwrap();
2835        runner.register_mappings(1, &offset_map1).await.expect("register 1 failed");
2836        runner.register_mappings(2, &offset_map2).await.expect("register 2 failed");
2837
2838        let (cmd1, payload1) = commands_rx.next().await.expect("expected first command");
2839        let (cmd2, payload2) = commands_rx.next().await.expect("expected second command");
2840
2841        assert_eq!(cmd1.key, 1);
2842        assert_eq!(cmd2.key, 2);
2843
2844        let (expected_payload1, _, _, expected_device_offset1) =
2845            super::offset_map_to_extents(&offset_map1, runner.block_size()).unwrap();
2846        let (expected_payload2, _, _, expected_device_offset2) =
2847            super::offset_map_to_extents(&offset_map2, runner.block_size()).unwrap();
2848        assert_eq!(cmd1.device_offset, expected_device_offset1);
2849        assert_eq!(cmd2.device_offset, expected_device_offset2);
2850        assert_eq!(payload1, expected_payload1);
2851        assert_eq!(payload2, expected_payload2);
2852
2853        runner.shutdown().await;
2854    }
2855
2856    #[fuchsia::test]
2857    async fn test_offset_map_to_extents_unaligned_length() {
2858        let offset_map = OffsetMap::new(vec![block_server::BlockOffsetMapping {
2859            target_block_offset: 34,
2860            length: 114654,
2861        }])
2862        .unwrap();
2863
2864        let (payload, logical_len, count, base_offset) =
2865            super::offset_map_to_extents(&offset_map, 512).unwrap();
2866
2867        assert_eq!(base_offset, 34 * 512);
2868        // 114654 * 512 = 58702848 bytes, rounded down to nearest 4KB is 58699776 bytes.
2869        assert_eq!(logical_len, 58699776);
2870        assert_eq!(count, 1);
2871        assert_eq!(payload.len(), 8);
2872    }
2873
2874    #[fuchsia::test]
2875    async fn test_offset_map_to_extents_multiple_mappings() {
2876        // First mapping is 4 KiB aligned (8 blocks of 512 = 4096 bytes).
2877        // Second mapping is unaligned (11 blocks of 512 = 5632 bytes -> rounded down to 4096
2878        // bytes).
2879        let offset_map = OffsetMap::new(vec![
2880            block_server::BlockOffsetMapping { target_block_offset: 34, length: 8 },
2881            block_server::BlockOffsetMapping { target_block_offset: 50, length: 11 },
2882        ])
2883        .unwrap();
2884
2885        let (payload, logical_len, count, base_offset) =
2886            super::offset_map_to_extents(&offset_map, 512).unwrap();
2887
2888        assert_eq!(base_offset, 34 * 512);
2889        assert_eq!(logical_len, 8192);
2890        assert_eq!(count, 2);
2891        assert_eq!(payload.len(), 16);
2892    }
2893
2894    #[fuchsia::test]
2895    async fn test_offset_map_to_extents_earlier_mapping_unaligned_fails() {
2896        // First mapping is not 4 KiB aligned (7 blocks of 512 = 3584 bytes).
2897        // Second mapping is 8 blocks (4096 bytes).
2898        let offset_map = OffsetMap::new(vec![
2899            block_server::BlockOffsetMapping { target_block_offset: 34, length: 7 },
2900            block_server::BlockOffsetMapping { target_block_offset: 50, length: 8 },
2901        ])
2902        .unwrap();
2903
2904        let err = super::offset_map_to_extents(&offset_map, 512).unwrap_err();
2905        assert_eq!(err.root_cause().downcast_ref::<zx::Status>(), Some(&zx::Status::NOT_SUPPORTED));
2906    }
2907
2908    #[fuchsia::test]
2909    async fn test_offset_map_to_extents_lowest_physical_block_base_offset() {
2910        // First logical mapping is at a higher physical block (50 * 512).
2911        // Second logical mapping is at a lower physical block (34 * 512).
2912        let offset_map = OffsetMap::new(vec![
2913            block_server::BlockOffsetMapping { target_block_offset: 50, length: 8 },
2914            block_server::BlockOffsetMapping { target_block_offset: 34, length: 8 },
2915        ])
2916        .unwrap();
2917
2918        let (payload, logical_len, count, base_offset) =
2919            super::offset_map_to_extents(&offset_map, 512).unwrap();
2920
2921        assert_eq!(base_offset, 34 * 512);
2922        assert_eq!(logical_len, 8192);
2923        assert_eq!(count, 2);
2924        assert_eq!(payload.len(), 16);
2925    }
2926}