1#![deny(missing_docs)]
8use anyhow::{Context as _, Error, anyhow};
9use fidl::endpoints::{DiscoverableProtocolMarker as _, Proxy as _};
10use fidl_fuchsia_device::{ControllerMarker, ControllerProxy, ControllerSynchronousProxy};
11use fidl_fuchsia_hardware_ramdisk::{Guid, RamdiskControllerMarker};
12use fs_management::filesystem::{BlockConnector, DirBasedBlockConnector};
13use fuchsia_component_client::{Service, connect_to_named_protocol_at_dir_root};
14use {
15 fidl_fuchsia_hardware_block as fhardware_block, fidl_fuchsia_hardware_block_volume as fvolume,
16 fidl_fuchsia_hardware_ramdisk as framdisk, fidl_fuchsia_io as fio,
17};
18
19const GUID_LEN: usize = 16;
20const DEV_PATH: &str = "/dev";
21const RAMCTL_PATH: &str = "sys/platform/ram-disk/ramctl";
22const BLOCK_EXTENSION: &str = "block";
23
24pub struct RamdiskClientBuilder {
26 ramdisk_source: RamdiskSource,
27 block_size: u64,
28 max_transfer_blocks: Option<u32>,
29 dev_root: Option<fio::DirectoryProxy>,
30 guid: Option<[u8; GUID_LEN]>,
31 use_v2: bool,
32 ramdisk_service: Option<fio::DirectoryProxy>,
33
34 publish: bool,
37}
38
39enum RamdiskSource {
40 Vmo { vmo: zx::Vmo },
41 Size { block_count: u64 },
42}
43
44impl RamdiskClientBuilder {
45 pub fn new(block_size: u64, block_count: u64) -> Self {
47 Self {
48 ramdisk_source: RamdiskSource::Size { block_count },
49 block_size,
50 max_transfer_blocks: None,
51 guid: None,
52 dev_root: None,
53 use_v2: false,
54 ramdisk_service: None,
55 publish: false,
56 }
57 }
58
59 pub fn new_with_vmo(vmo: zx::Vmo, block_size: Option<u64>) -> Self {
61 Self {
62 ramdisk_source: RamdiskSource::Vmo { vmo },
63 block_size: block_size.unwrap_or(0),
64 max_transfer_blocks: None,
65 guid: None,
66 dev_root: None,
67 use_v2: false,
68 ramdisk_service: None,
69 publish: false,
70 }
71 }
72
73 pub fn dev_root(mut self, dev_root: fio::DirectoryProxy) -> Self {
75 self.dev_root = Some(dev_root);
76 self
77 }
78
79 pub fn guid(mut self, guid: [u8; GUID_LEN]) -> Self {
81 self.guid = Some(guid);
82 self
83 }
84
85 pub fn max_transfer_blocks(mut self, value: u32) -> Self {
87 self.max_transfer_blocks = Some(value);
88 self
89 }
90
91 pub fn use_v2(mut self) -> Self {
93 self.use_v2 = true;
94 self
95 }
96
97 pub fn ramdisk_service(mut self, service: fio::DirectoryProxy) -> Self {
99 self.ramdisk_service = Some(service);
100 self
101 }
102
103 pub fn publish(mut self) -> Self {
105 self.publish = true;
106 self
107 }
108
109 pub async fn build(self) -> Result<RamdiskClient, Error> {
111 let Self {
112 ramdisk_source,
113 block_size,
114 max_transfer_blocks,
115 guid,
116 dev_root,
117 use_v2,
118 ramdisk_service,
119 publish,
120 } = self;
121
122 if use_v2 {
123 let service = match ramdisk_service {
125 Some(s) => {
126 Service::from_service_dir_proxy(s, fidl_fuchsia_hardware_ramdisk::ServiceMarker)
127 }
128 None => Service::open(fidl_fuchsia_hardware_ramdisk::ServiceMarker)?,
129 };
130 let ramdisk_controller = service.watch_for_any().await?.connect_to_controller()?;
131
132 let type_guid = guid.map(|guid| Guid { value: guid });
133
134 let options = match ramdisk_source {
135 RamdiskSource::Vmo { vmo } => framdisk::Options {
136 vmo: Some(vmo),
137 block_size: if block_size == 0 {
138 None
139 } else {
140 Some(block_size.try_into().unwrap())
141 },
142 type_guid,
143 publish: Some(publish),
144 max_transfer_blocks,
145 ..Default::default()
146 },
147 RamdiskSource::Size { block_count } => framdisk::Options {
148 block_count: Some(block_count),
149 block_size: Some(block_size.try_into().unwrap()),
150 type_guid,
151 publish: Some(publish),
152 max_transfer_blocks,
153 ..Default::default()
154 },
155 };
156
157 let (outgoing, event) =
158 ramdisk_controller.create(options).await?.map_err(|s| zx::Status::from_raw(s))?;
159
160 RamdiskClient::new_v2(outgoing.into_proxy(), event)
161 } else {
162 let dev_root = if let Some(dev_root) = dev_root {
163 dev_root
164 } else {
165 fuchsia_fs::directory::open_in_namespace(DEV_PATH, fio::PERM_READABLE)
166 .with_context(|| format!("open {}", DEV_PATH))?
167 };
168 let ramdisk_controller = device_watcher::recursive_wait_and_open::<
169 RamdiskControllerMarker,
170 >(&dev_root, RAMCTL_PATH)
171 .await
172 .with_context(|| format!("waiting for {}", RAMCTL_PATH))?;
173 let type_guid = guid.map(|guid| Guid { value: guid });
174 let name = match ramdisk_source {
175 RamdiskSource::Vmo { vmo } => ramdisk_controller
176 .create_from_vmo_with_params(vmo, block_size, type_guid.as_ref())
177 .await?
178 .map_err(zx::Status::from_raw)
179 .context("creating ramdisk from vmo")?,
180 RamdiskSource::Size { block_count } => ramdisk_controller
181 .create(block_size, block_count, type_guid.as_ref())
182 .await?
183 .map_err(zx::Status::from_raw)
184 .with_context(|| format!("creating ramdisk with {} blocks", block_count))?,
185 };
186 let name = name.ok_or_else(|| anyhow!("Failed to get instance name"))?;
187 RamdiskClient::new(dev_root, &name).await
188 }
189 }
190}
191
192pub enum RamdiskClient {
196 V1 {
198 block_dir: fio::DirectoryProxy,
200
201 block_controller: ControllerProxy,
203
204 ramdisk_controller: Option<ControllerProxy>,
206 },
207 V2 {
209 outgoing: fio::DirectoryProxy,
211
212 _event: zx::EventPair,
214 },
215}
216
217impl RamdiskClient {
218 async fn new(dev_root: fio::DirectoryProxy, instance_name: &str) -> Result<Self, Error> {
219 let ramdisk_path = format!("{RAMCTL_PATH}/{instance_name}");
220 let ramdisk_controller_path = format!("{ramdisk_path}/device_controller");
221 let block_path = format!("{ramdisk_path}/{BLOCK_EXTENSION}");
222
223 let ramdisk_controller = device_watcher::recursive_wait_and_open::<ControllerMarker>(
225 &dev_root,
226 &ramdisk_controller_path,
227 )
228 .await
229 .with_context(|| format!("waiting for {}", &ramdisk_controller_path))?;
230
231 let block_dir = device_watcher::recursive_wait_and_open_directory(&dev_root, &block_path)
233 .await
234 .with_context(|| format!("waiting for {}", &block_path))?;
235
236 let block_controller = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
237 &block_dir,
238 "device_controller",
239 )
240 .with_context(|| {
241 format!("opening block controller at {}/device_controller", &block_path)
242 })?;
243
244 Ok(Self::V1 { block_dir, block_controller, ramdisk_controller: Some(ramdisk_controller) })
245 }
246
247 fn new_v2(outgoing: fio::DirectoryProxy, event: zx::EventPair) -> Result<Self, Error> {
248 Ok(Self::V2 { outgoing, _event: event })
249 }
250
251 pub fn builder(block_size: u64, block_count: u64) -> RamdiskClientBuilder {
253 RamdiskClientBuilder::new(block_size, block_count)
254 }
255
256 pub async fn create(block_size: u64, block_count: u64) -> Result<Self, Error> {
258 Self::builder(block_size, block_count).build().await
259 }
260
261 pub fn as_controller(&self) -> Option<&ControllerProxy> {
263 match self {
264 Self::V1 { block_controller, .. } => Some(block_controller),
265 Self::V2 { .. } => None,
266 }
267 }
268
269 pub fn as_dir(&self) -> Option<&fio::DirectoryProxy> {
271 match self {
272 Self::V1 { block_dir, .. } => Some(block_dir),
273 Self::V2 { .. } => None,
274 }
275 }
276
277 pub fn open(&self) -> Result<fidl::endpoints::ClientEnd<fhardware_block::BlockMarker>, Error> {
279 let (client, server_end) = fidl::endpoints::create_endpoints();
280 self.connect(server_end)?;
281 Ok(client)
282 }
283
284 pub fn connector(&self) -> Result<Box<dyn BlockConnector>, Error> {
286 match self {
287 Self::V1 { .. } => {
288 let block_dir = fuchsia_fs::directory::clone(
295 self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?,
296 )?;
297 Ok(Box::new(DirBasedBlockConnector::new(block_dir, ".".to_string())))
298 }
299 Self::V2 { outgoing, .. } => {
300 let block_dir = fuchsia_fs::directory::clone(outgoing)?;
301 Ok(Box::new(DirBasedBlockConnector::new(
302 block_dir,
303 format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
304 )))
305 }
306 }
307 }
308
309 pub fn connect(
311 &self,
312 server_end: fidl::endpoints::ServerEnd<fhardware_block::BlockMarker>,
313 ) -> Result<(), Error> {
314 match self {
315 Self::V1 { .. } => {
316 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
317 Ok(block_dir.open(
318 ".",
319 fio::Flags::empty(),
320 &fio::Options::default(),
321 server_end.into_channel(),
322 )?)
323 }
324 Self::V2 { outgoing, .. } => Ok(outgoing.open(
325 &format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
326 fio::Flags::empty(),
327 &fio::Options::default(),
328 server_end.into_channel(),
329 )?),
330 }
331 }
332
333 pub fn open_controller(&self) -> Result<ControllerProxy, Error> {
335 match self {
336 Self::V1 { .. } => {
337 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
338 let controller_proxy = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
339 block_dir,
340 "device_controller",
341 )
342 .context("opening block controller")?;
343 Ok(controller_proxy)
344 }
345 Self::V2 { .. } => Err(anyhow!("Not supported")),
346 }
347 }
348
349 pub async fn destroy(mut self) -> Result<(), Error> {
353 match &mut self {
354 Self::V1 { ramdisk_controller, .. } => {
355 let ramdisk_controller = ramdisk_controller
356 .take()
357 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
358 let () = ramdisk_controller
359 .schedule_unbind()
360 .await
361 .context("unbind transport")?
362 .map_err(zx::Status::from_raw)
363 .context("unbind response")?;
364 }
365 Self::V2 { .. } => {} }
367 Ok(())
368 }
369
370 pub async fn destroy_and_wait_for_removal(mut self) -> Result<(), Error> {
374 match &mut self {
375 Self::V1 { block_controller, ramdisk_controller, .. } => {
376 let ramdisk_controller = ramdisk_controller
383 .take()
384 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
385 let () = ramdisk_controller
386 .schedule_unbind()
387 .await
388 .context("unbind transport")?
389 .map_err(zx::Status::from_raw)
390 .context("unbind response")?;
391 let _: (zx::Signals, zx::Signals) = futures::future::try_join(
392 block_controller.on_closed(),
393 ramdisk_controller.on_closed(),
394 )
395 .await
396 .context("on closed")?;
397 }
398 Self::V2 { .. } => {}
399 }
400 Ok(())
401 }
402
403 pub fn forget(mut self) -> Result<(), Error> {
408 match &mut self {
409 Self::V1 { ramdisk_controller, .. } => {
410 let _ = ramdisk_controller.take();
411 Ok(())
412 }
413 Self::V2 { .. } => Err(anyhow!("Not supported")),
414 }
415 }
416}
417
418impl BlockConnector for RamdiskClient {
419 fn connect_channel_to_volume(
420 &self,
421 server_end: fidl::endpoints::ServerEnd<fidl_fuchsia_hardware_block_volume::VolumeMarker>,
422 ) -> Result<(), Error> {
423 self.connect(server_end.into_channel().into())
424 }
425}
426
427impl Drop for RamdiskClient {
428 fn drop(&mut self) {
429 if let Self::V1 { ramdisk_controller, .. } = self {
430 if let Some(ramdisk_controller) = ramdisk_controller.take() {
431 let _: Result<Result<(), _>, _> = ControllerSynchronousProxy::new(
432 ramdisk_controller.into_channel().unwrap().into(),
433 )
434 .schedule_unbind(zx::MonotonicInstant::INFINITE);
435 }
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443 use assert_matches::assert_matches;
444
445 const TEST_GUID: [u8; GUID_LEN] = [
448 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
449 0x10,
450 ];
451
452 #[fuchsia::test]
453 async fn create_get_dir_proxy_destroy() {
454 let ramdisk =
456 RamdiskClient::builder(512, 2048).build().await.expect("failed to create ramdisk");
457 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
458 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
459 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
460 }
461
462 #[fuchsia::test]
463 async fn create_with_dev_root_and_guid_get_dir_proxy_destroy() {
464 let dev_root = fuchsia_fs::directory::open_in_namespace(DEV_PATH, fio::PERM_READABLE)
465 .with_context(|| format!("open {}", DEV_PATH))
466 .expect("failed to create directory proxy");
467 let ramdisk = RamdiskClient::builder(512, 2048)
468 .dev_root(dev_root)
469 .guid(TEST_GUID)
470 .build()
471 .await
472 .expect("failed to create ramdisk");
473 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
474 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
475 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
476 }
477
478 #[fuchsia::test]
479 async fn create_with_guid_get_dir_proxy_destroy() {
480 let ramdisk = RamdiskClient::builder(512, 2048)
481 .guid(TEST_GUID)
482 .build()
483 .await
484 .expect("failed to create ramdisk");
485 let ramdisk_dir = ramdisk.as_dir().expect("invalid directory proxy");
486 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
487 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
488 }
489
490 #[fuchsia::test]
491 async fn create_open_destroy() {
492 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
493 let client = ramdisk.open().unwrap().into_proxy();
494 client.get_info().await.expect("get_info failed").unwrap();
495 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
496 }
498
499 #[fuchsia::test]
500 async fn create_open_forget() {
501 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
502 let client = ramdisk.open().unwrap().into_proxy();
503 client.get_info().await.expect("get_info failed").unwrap();
504 assert!(ramdisk.forget().is_ok());
505 client.get_info().await.expect("get_info failed").unwrap();
507 }
508
509 #[fuchsia::test]
510 async fn destroy_and_wait_for_removal() {
511 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
512 let dir = fuchsia_fs::directory::clone(ramdisk.as_dir().unwrap()).unwrap();
513
514 assert_matches!(
515 fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(),
516 [
517 fuchsia_fs::directory::DirEntry {
518 name: name1,
519 kind: fuchsia_fs::directory::DirentKind::File,
520 },
521 fuchsia_fs::directory::DirEntry {
522 name: name2,
523 kind: fuchsia_fs::directory::DirentKind::File,
524 },
525 fuchsia_fs::directory::DirEntry {
526 name: name3,
527 kind: fuchsia_fs::directory::DirentKind::File,
528 },
529 ] if [name1, name2, name3] == [
530 fidl_fuchsia_device_fs::DEVICE_CONTROLLER_NAME,
531 fidl_fuchsia_device_fs::DEVICE_PROTOCOL_NAME,
532 fidl_fuchsia_device_fs::DEVICE_TOPOLOGY_NAME,
533 ]
534 );
535
536 let () = ramdisk.destroy_and_wait_for_removal().await.unwrap();
537
538 assert_matches!(fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(), []);
539 }
540}