Skip to main content

ramdevice_client/
lib.rs

1// Copyright 2019 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
5//! A safe rust wrapper for creating and using ramdisks.
6
7#![deny(missing_docs)]
8use anyhow::Error;
9use fidl::endpoints::DiscoverableProtocolMarker as _;
10use fidl_fuchsia_hardware_ramdisk as framdisk;
11use fidl_fuchsia_hardware_ramdisk::Guid;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_storage_block as fblock;
14use fs_management::filesystem::{BlockConnector, DirBasedBlockConnector};
15use fuchsia_component_client::Service;
16
17const GUID_LEN: usize = 16;
18
19/// A type to help construct a [`RamdeviceClient`] optionally from a VMO.
20pub struct RamdiskClientBuilder {
21    ramdisk_source: RamdiskSource,
22    block_size: u64,
23    max_transfer_blocks: Option<u32>,
24    guid: Option<[u8; GUID_LEN]>,
25    ramdisk_service: Option<fio::DirectoryProxy>,
26    device_flags: Option<fblock::DeviceFlag>,
27
28    // Whether to publish this ramdisk as a fuchsia.hardware.block.volume.Service service.
29    publish: bool,
30}
31
32enum RamdiskSource {
33    Vmo { vmo: zx::Vmo },
34    Size { block_count: u64 },
35}
36
37impl RamdiskClientBuilder {
38    /// Create a new ramdisk builder
39    pub fn new(block_size: u64, block_count: u64) -> Self {
40        Self {
41            ramdisk_source: RamdiskSource::Size { block_count },
42            block_size,
43            max_transfer_blocks: None,
44            guid: None,
45            ramdisk_service: None,
46            publish: false,
47            device_flags: None,
48        }
49    }
50
51    /// Create a new ramdisk builder with a vmo
52    pub fn new_with_vmo(vmo: zx::Vmo, block_size: Option<u64>) -> Self {
53        Self {
54            ramdisk_source: RamdiskSource::Vmo { vmo },
55            block_size: block_size.unwrap_or(0),
56            max_transfer_blocks: None,
57            guid: None,
58            ramdisk_service: None,
59            publish: false,
60            device_flags: None,
61        }
62    }
63
64    /// Initialize the ramdisk with the given GUID, which can be queried from the ramdisk instance.
65    pub fn guid(mut self, guid: [u8; GUID_LEN]) -> Self {
66        self.guid = Some(guid);
67        self
68    }
69
70    /// Sets the maximum transfer size.
71    pub fn max_transfer_blocks(mut self, value: u32) -> Self {
72        self.max_transfer_blocks = Some(value);
73        self
74    }
75
76    /// Specifies the ramdisk service.
77    pub fn ramdisk_service(mut self, service: fio::DirectoryProxy) -> Self {
78        self.ramdisk_service = Some(service);
79        self
80    }
81
82    /// Publish this ramdisk as a fuchsia.hardware.block.volume.Service service.
83    pub fn publish(mut self) -> Self {
84        self.publish = true;
85        self
86    }
87
88    /// Use the provided device flags.
89    pub fn device_flags(mut self, device_flags: fblock::DeviceFlag) -> Self {
90        self.device_flags = Some(device_flags);
91        self
92    }
93
94    /// Create the ramdisk.
95    pub async fn build(self) -> Result<RamdiskClient, Error> {
96        let Self {
97            ramdisk_source,
98            block_size,
99            max_transfer_blocks,
100            guid,
101            ramdisk_service,
102            publish,
103            device_flags,
104        } = self;
105
106        // Pick the first service instance we find.
107        let service = match ramdisk_service {
108            Some(s) => {
109                Service::from_service_dir_proxy(s, fidl_fuchsia_hardware_ramdisk::ServiceMarker)
110            }
111            None => Service::open(fidl_fuchsia_hardware_ramdisk::ServiceMarker)?,
112        };
113        let ramdisk_controller = service.watch_for_any().await?.connect_to_controller()?;
114
115        let type_guid = guid.map(|guid| Guid { value: guid });
116
117        let options = match ramdisk_source {
118            RamdiskSource::Vmo { vmo } => framdisk::Options {
119                vmo: Some(vmo),
120                block_size: if block_size == 0 {
121                    None
122                } else {
123                    Some(block_size.try_into().unwrap())
124                },
125                type_guid,
126                publish: Some(publish),
127                max_transfer_blocks,
128                device_flags,
129                ..Default::default()
130            },
131            RamdiskSource::Size { block_count } => framdisk::Options {
132                block_count: Some(block_count),
133                block_size: Some(block_size.try_into().unwrap()),
134                type_guid,
135                publish: Some(publish),
136                max_transfer_blocks,
137                device_flags,
138                ..Default::default()
139            },
140        };
141
142        let (outgoing, event) =
143            ramdisk_controller.create(options).await?.map_err(|s| zx::Status::from_raw(s))?;
144
145        let outgoing = outgoing.into_proxy();
146
147        RamdiskClient::new(outgoing, event)
148    }
149}
150
151/// A client for managing a ramdisk. This can be created with the [`RamdiskClient::create`]
152/// function or through the type returned by [`RamdiskClient::builder`] to specify additional
153/// options.
154pub struct RamdiskClient {
155    /// The outgoing directory for the ram-disk.
156    outgoing: fio::DirectoryProxy,
157
158    /// The event that keeps the ramdisk alive.
159    _event: zx::EventPair,
160}
161
162impl RamdiskClient {
163    fn new(outgoing: fio::DirectoryProxy, event: zx::EventPair) -> Result<Self, Error> {
164        Ok(Self { outgoing, _event: event })
165    }
166
167    /// Create a new ramdisk builder with the given block_size and block_count.
168    pub fn builder(block_size: u64, block_count: u64) -> RamdiskClientBuilder {
169        RamdiskClientBuilder::new(block_size, block_count)
170    }
171
172    /// Create a new ramdisk.
173    pub async fn create(block_size: u64, block_count: u64) -> Result<Self, Error> {
174        Self::builder(block_size, block_count).build().await
175    }
176
177    /// Returns the directory proxy for the ramdisk's outgoing directory.
178    pub fn outgoing(&self) -> &fio::DirectoryProxy {
179        &self.outgoing
180    }
181
182    /// Get an open channel to the underlying ramdevice.
183    pub fn open(&self) -> Result<fidl::endpoints::ClientEnd<fblock::BlockMarker>, Error> {
184        let (client, server_end) = fidl::endpoints::create_endpoints();
185        self.connect(server_end)?;
186        Ok(client)
187    }
188
189    /// Gets a connector for the Block protocol of the ramdisk.
190    pub fn connector(&self) -> Result<Box<dyn BlockConnector>, Error> {
191        let block_dir = fuchsia_fs::directory::clone(&self.outgoing)?;
192        Ok(Box::new(DirBasedBlockConnector::new(
193            block_dir,
194            format!("svc/{}", fblock::BlockMarker::PROTOCOL_NAME),
195        )))
196    }
197
198    /// Get an open channel to the underlying ramdevice.
199    pub fn connect(
200        &self,
201        server_end: fidl::endpoints::ServerEnd<fblock::BlockMarker>,
202    ) -> Result<(), Error> {
203        Ok(self.outgoing.open(
204            &format!("svc/{}", fblock::BlockMarker::PROTOCOL_NAME),
205            fio::Flags::empty(),
206            &fio::Options::default(),
207            server_end.into_channel(),
208        )?)
209    }
210
211    /// Get an open channel to the Ramdisk protocol.
212    pub fn open_ramdisk(&self) -> Result<framdisk::RamdiskProxy, Error> {
213        let (client, server) = fidl::endpoints::create_proxy::<framdisk::RamdiskMarker>();
214        self.outgoing.open(
215            &format!("svc/{}", framdisk::RamdiskMarker::PROTOCOL_NAME),
216            fio::Flags::empty(),
217            &fio::Options::default(),
218            server.into_channel(),
219        )?;
220        Ok(client)
221    }
222
223    /// Consume the client and return the event that keeps the ramdisk alive.
224    pub fn into_event(self) -> zx::EventPair {
225        self._event
226    }
227}
228
229impl BlockConnector for RamdiskClient {
230    fn connect_channel_to_block(
231        &self,
232        server_end: fidl::endpoints::ServerEnd<fidl_fuchsia_storage_block::BlockMarker>,
233    ) -> Result<(), Error> {
234        self.connect(server_end.into_channel().into())
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    // Note that if these tests flake, all downstream tests that depend on this crate may too.
243
244    const TEST_GUID: [u8; GUID_LEN] = [
245        0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
246        0x10,
247    ];
248
249    #[fuchsia::test]
250    async fn create_get_dir_proxy_destroy() {
251        // just make sure all the functions are hooked up properly.
252        let ramdisk =
253            RamdiskClient::builder(512, 2048).build().await.expect("failed to create ramdisk");
254        let ramdisk_dir = &ramdisk.outgoing;
255        fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
256    }
257
258    #[fuchsia::test]
259    async fn create_with_guid_get_dir_proxy_destroy() {
260        let ramdisk = RamdiskClient::builder(512, 2048)
261            .guid(TEST_GUID)
262            .build()
263            .await
264            .expect("failed to create ramdisk");
265        let ramdisk_dir = &ramdisk.outgoing;
266        fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
267    }
268
269    #[fuchsia::test]
270    async fn create_open_destroy() {
271        let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
272        let client = ramdisk.open().unwrap().into_proxy();
273        client.get_info().await.expect("get_info failed").unwrap();
274        // The ramdisk will be scheduled to be unbound, so `client` may be valid for some time.
275    }
276
277    #[fuchsia::test]
278    async fn create_open_into_event() {
279        let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
280        let client = ramdisk.open().unwrap().into_proxy();
281        client.get_info().await.expect("get_info failed").unwrap();
282        let _event = ramdisk.into_event();
283        // We should succeed calling `get_info` as the ramdisk should still exist.
284        client.get_info().await.expect("get_info failed").unwrap();
285    }
286}