Skip to main content

gpt_component/
partition.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.
4use crate::gpt::GptPartition;
5use anyhow::Error;
6use block_client::{ReadOptions, VmoId, WriteOptions};
7use block_server::async_interface::{PassthroughSession, SessionManager};
8use block_server::{DeviceInfo, OffsetMap};
9use fidl::endpoints::{ControlHandle, RequestStream};
10use fidl_fuchsia_storage_block as fblock;
11use fuchsia_async as fasync;
12
13use fuchsia_sync::Mutex;
14use std::borrow::Cow;
15use std::collections::BTreeMap;
16use std::num::NonZero;
17use std::sync::{Arc, Weak};
18
19/// A wrapper around a VmoId which keeps it active until all requests which use the Vmoid are
20/// complete.  Strong references are held by ongoing requests.
21pub struct VmoIdWrapper {
22    partition: Weak<GptPartition>,
23    vmo_id: VmoId,
24}
25
26impl std::ops::Deref for VmoIdWrapper {
27    type Target = VmoId;
28    fn deref(&self) -> &Self::Target {
29        &self.vmo_id
30    }
31}
32
33impl Drop for VmoIdWrapper {
34    fn drop(&mut self) {
35        // Turn it into an ID so that if the spawned task is dropped, the assertion in VmoId::drop
36        // doesn't fire.  It will mean the ID is leaked, but it's most likely that the server is
37        // being shut down anyway so it shouldn't matter.
38        let vmo_id = self.vmo_id.take().into_id();
39        if let Some(partition) = self.partition.upgrade() {
40            fasync::Task::spawn(async move {
41                if let Err(e) = partition.detach_vmo(VmoId::new(vmo_id)).await {
42                    log::error!("detach_vmo failed: {:?}", e);
43                }
44            })
45            .detach();
46        }
47    }
48}
49
50/// PartitionBackend is an implementation of block_server's Interface which is backed by a windowed
51/// view of the underlying GPT device.
52pub struct PartitionBackend {
53    partition: Arc<GptPartition>,
54    vmo_keys_to_vmoids_map: Mutex<BTreeMap<usize, Arc<VmoIdWrapper>>>,
55    offset_map: block_server::OffsetMap,
56}
57
58impl block_server::async_interface::Interface for PartitionBackend {
59    async fn open_session(
60        &self,
61        session_manager: Arc<SessionManager<Self>>,
62        stream: fblock::SessionRequestStream,
63        offset_map: OffsetMap,
64        block_size: u32,
65    ) -> Result<(), Error> {
66        if !offset_map.is_empty() {
67            // For now, we don't support double-passthrough.  We could as needed for nested GPT.
68            let _ = stream.control_handle().shutdown_with_epitaph(zx::Status::NOT_SUPPORTED);
69            anyhow::bail!("Client-provided offset maps are not supported");
70        }
71        if self.offset_map.is_empty() {
72            return session_manager
73                .serve_session(
74                    stream,
75                    OffsetMap::empty(),
76                    self.get_info().max_transfer_blocks(),
77                    block_size,
78                )
79                .await;
80        }
81        let (proxy, server_end) = fidl::endpoints::create_proxy::<fblock::SessionMarker>();
82        self.partition.open_passthrough_session(server_end, &self.offset_map);
83        let passthrough = PassthroughSession::new(proxy);
84        passthrough.serve(stream).await
85    }
86
87    async fn on_attach_vmo(&self, vmo: &zx::Vmo) -> Result<(), zx::Status> {
88        let key = std::ptr::from_ref(vmo) as usize;
89        // SAFETY: GPT does not map VMOs in its own process, so it cannot violate Rust's aliasing
90        // guarantees.  Safety is delegated to the client process that mapped the VMO.
91        let vmo_id = unsafe { self.partition.attach_vmo(vmo) }.await?;
92        self.vmo_keys_to_vmoids_map.lock().insert(
93            key,
94            Arc::new(VmoIdWrapper { partition: Arc::downgrade(&self.partition), vmo_id }),
95        );
96        Ok(())
97    }
98
99    fn on_detach_vmo(&self, vmo: &zx::Vmo) {
100        // Note that we will not immediately detach the VMO.  This happens when the last reference
101        // to it is dropped (in [`VmoIdWrapper::drop`]).
102        let key = std::ptr::from_ref(vmo) as usize;
103        self.vmo_keys_to_vmoids_map.lock().remove(&key);
104    }
105
106    fn get_info(&self) -> Cow<'_, DeviceInfo> {
107        Cow::Owned(self.partition.get_info())
108    }
109
110    async fn read(
111        &self,
112        device_block_offset: u64,
113        block_count: u32,
114        vmo: &Arc<zx::Vmo>,
115        vmo_offset: u64, // *bytes* not blocks
116        opts: ReadOptions,
117        trace_flow_id: Option<NonZero<u64>>,
118    ) -> Result<(), zx::Status> {
119        let vmo_id = self.get_vmoid(vmo)?;
120        self.partition
121            .read(device_block_offset, block_count, &vmo_id, vmo_offset, opts, trace_flow_id)
122            .await
123    }
124
125    async fn write(
126        &self,
127        device_block_offset: u64,
128        length: u32,
129        vmo: &Arc<zx::Vmo>,
130        vmo_offset: u64, // *bytes* not blocks
131        opts: WriteOptions,
132        trace_flow_id: Option<NonZero<u64>>,
133    ) -> Result<(), zx::Status> {
134        let vmo_id = self.get_vmoid(vmo)?;
135        self.partition
136            .write(device_block_offset, length, &vmo_id, vmo_offset, opts, trace_flow_id)
137            .await
138    }
139
140    async fn flush(&self, trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
141        self.partition.flush(trace_flow_id).await
142    }
143
144    async fn trim(
145        &self,
146        device_block_offset: u64,
147        block_count: u32,
148        trace_flow_id: Option<NonZero<u64>>,
149    ) -> Result<(), zx::Status> {
150        self.partition.trim(device_block_offset, block_count, trace_flow_id).await
151    }
152}
153
154impl PartitionBackend {
155    #[cfg(test)]
156    pub fn passthrough(&self) -> bool {
157        !self.offset_map.is_empty()
158    }
159
160    #[cfg(test)]
161    pub fn vmo_count(&self) -> usize {
162        self.vmo_keys_to_vmoids_map.lock().len()
163    }
164
165    /// If `offset_map` is non-empty, the partition will pass through requests using the provided
166    /// offset map.  Otherwise, the partition will proxy I/O requests (and `read`, `write`, etc will
167    /// be called on this instance).
168    pub fn new(partition: Arc<GptPartition>, offset_map: block_server::OffsetMap) -> Arc<Self> {
169        Arc::new(Self {
170            partition,
171            offset_map,
172            vmo_keys_to_vmoids_map: Mutex::new(BTreeMap::new()),
173        })
174    }
175
176    /// Updates the info.
177    pub fn update_info(&self, info: gpt::PartitionInfo) {
178        self.partition.update_info(info)
179    }
180
181    fn get_vmoid(&self, vmo: &zx::Vmo) -> Result<Arc<VmoIdWrapper>, zx::Status> {
182        let key = std::ptr::from_ref(vmo) as usize;
183        self.vmo_keys_to_vmoids_map.lock().get(&key).map(Arc::clone).ok_or(zx::Status::NOT_FOUND)
184    }
185}