1#![deny(missing_docs)]
8use anyhow::{anyhow, Context as _, Error};
9use fidl::endpoints::{ClientEnd, 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::{connect_to_named_protocol_at_dir_root, Service};
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: Option<fio::DirectoryProxy>,
200
201 block_controller: Option<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 {
245 block_dir: Some(block_dir),
246 block_controller: Some(block_controller),
247 ramdisk_controller: Some(ramdisk_controller),
248 })
249 }
250
251 fn new_v2(outgoing: fio::DirectoryProxy, event: zx::EventPair) -> Result<Self, Error> {
252 Ok(Self::V2 { outgoing, _event: event })
253 }
254
255 pub fn builder(block_size: u64, block_count: u64) -> RamdiskClientBuilder {
257 RamdiskClientBuilder::new(block_size, block_count)
258 }
259
260 pub async fn create(block_size: u64, block_count: u64) -> Result<Self, Error> {
262 Self::builder(block_size, block_count).build().await
263 }
264
265 pub fn as_controller(&self) -> Option<&ControllerProxy> {
267 match self {
268 Self::V1 { block_controller, .. } => block_controller.as_ref(),
269 Self::V2 { .. } => None,
270 }
271 }
272
273 pub fn take_controller(&mut self) -> Option<ControllerProxy> {
275 match self {
276 Self::V1 { block_controller, .. } => block_controller.take(),
277 Self::V2 { .. } => None,
278 }
279 }
280
281 pub fn as_dir(&self) -> Option<&fio::DirectoryProxy> {
283 match self {
284 Self::V1 { block_dir, .. } => block_dir.as_ref(),
285 Self::V2 { .. } => None,
286 }
287 }
288
289 pub fn take_dir(&mut self) -> Option<fio::DirectoryProxy> {
291 match self {
292 Self::V1 { block_dir, .. } => block_dir.take(),
293 Self::V2 { .. } => None,
294 }
295 }
296
297 pub fn open(&self) -> Result<fidl::endpoints::ClientEnd<fhardware_block::BlockMarker>, Error> {
299 let (client, server_end) = fidl::endpoints::create_endpoints();
300 self.connect(server_end)?;
301 Ok(client)
302 }
303
304 pub fn connector(&self) -> Result<Box<dyn BlockConnector>, Error> {
306 match self {
307 Self::V1 { .. } => {
308 let block_dir = fuchsia_fs::directory::clone(
315 self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?,
316 )?;
317 Ok(Box::new(DirBasedBlockConnector::new(block_dir, ".".to_string())))
318 }
319 Self::V2 { outgoing, .. } => {
320 let block_dir = fuchsia_fs::directory::clone(outgoing)?;
321 Ok(Box::new(DirBasedBlockConnector::new(
322 block_dir,
323 format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
324 )))
325 }
326 }
327 }
328
329 pub fn connect(
331 &self,
332 server_end: fidl::endpoints::ServerEnd<fhardware_block::BlockMarker>,
333 ) -> Result<(), Error> {
334 match self {
335 Self::V1 { .. } => {
336 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
337 Ok(block_dir.open(
338 ".",
339 fio::Flags::empty(),
340 &fio::Options::default(),
341 server_end.into_channel(),
342 )?)
343 }
344 Self::V2 { outgoing, .. } => Ok(outgoing.open(
345 &format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
346 fio::Flags::empty(),
347 &fio::Options::default(),
348 server_end.into_channel(),
349 )?),
350 }
351 }
352
353 pub fn open_controller(
355 &self,
356 ) -> Result<fidl::endpoints::ClientEnd<fidl_fuchsia_device::ControllerMarker>, Error> {
357 match self {
358 Self::V1 { .. } => {
359 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
360 let controller_proxy = connect_to_named_protocol_at_dir_root::<
361 fidl_fuchsia_device::ControllerMarker,
362 >(block_dir, "device_controller")?;
363 Ok(ClientEnd::new(controller_proxy.into_channel().unwrap().into()))
364 }
365 Self::V2 { .. } => Err(anyhow!("Not supported")),
366 }
367 }
368
369 pub async fn destroy(mut self) -> Result<(), Error> {
373 match &mut self {
374 Self::V1 { ramdisk_controller, .. } => {
375 let ramdisk_controller = ramdisk_controller
376 .take()
377 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
378 let () = ramdisk_controller
379 .schedule_unbind()
380 .await
381 .context("unbind transport")?
382 .map_err(zx::Status::from_raw)
383 .context("unbind response")?;
384 }
385 Self::V2 { .. } => {} }
387 Ok(())
388 }
389
390 pub async fn destroy_and_wait_for_removal(mut self) -> Result<(), Error> {
394 match &mut self {
395 Self::V1 { block_controller, ramdisk_controller, .. } => {
396 let block_controller = block_controller
404 .take()
405 .ok_or_else(|| anyhow!("block controller is invalid"))?;
406 let ramdisk_controller = ramdisk_controller
407 .take()
408 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
409 let () = ramdisk_controller
410 .schedule_unbind()
411 .await
412 .context("unbind transport")?
413 .map_err(zx::Status::from_raw)
414 .context("unbind response")?;
415 let _: (zx::Signals, zx::Signals) = futures::future::try_join(
416 block_controller.on_closed(),
417 ramdisk_controller.on_closed(),
418 )
419 .await
420 .context("on closed")?;
421 }
422 Self::V2 { .. } => {}
423 }
424 Ok(())
425 }
426
427 pub fn forget(mut self) -> Result<(), Error> {
432 match &mut self {
433 Self::V1 { ramdisk_controller, .. } => {
434 let _: ControllerProxy = ramdisk_controller
435 .take()
436 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
437 Ok(())
438 }
439 Self::V2 { .. } => Err(anyhow!("Not supported")),
440 }
441 }
442}
443
444impl BlockConnector for RamdiskClient {
445 fn connect_channel_to_volume(
446 &self,
447 server_end: fidl::endpoints::ServerEnd<fidl_fuchsia_hardware_block_volume::VolumeMarker>,
448 ) -> Result<(), Error> {
449 self.connect(server_end.into_channel().into())
450 }
451}
452
453impl Drop for RamdiskClient {
454 fn drop(&mut self) {
455 if let Self::V1 { ramdisk_controller, .. } = self {
456 if let Some(ramdisk_controller) = ramdisk_controller.take() {
457 let _: Result<Result<(), _>, _> = ControllerSynchronousProxy::new(
458 ramdisk_controller.into_channel().unwrap().into(),
459 )
460 .schedule_unbind(zx::MonotonicInstant::INFINITE);
461 }
462 }
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use assert_matches::assert_matches;
470
471 const TEST_GUID: [u8; GUID_LEN] = [
474 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
475 0x10,
476 ];
477
478 #[fuchsia::test]
479 async fn create_get_dir_proxy_destroy() {
480 let ramdisk =
482 RamdiskClient::builder(512, 2048).build().await.expect("failed to create ramdisk");
483 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
484 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
485 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
486 }
487
488 #[fuchsia::test]
489 async fn create_with_dev_root_and_guid_get_dir_proxy_destroy() {
490 let dev_root = fuchsia_fs::directory::open_in_namespace(DEV_PATH, fio::PERM_READABLE)
491 .with_context(|| format!("open {}", DEV_PATH))
492 .expect("failed to create directory proxy");
493 let ramdisk = RamdiskClient::builder(512, 2048)
494 .dev_root(dev_root)
495 .guid(TEST_GUID)
496 .build()
497 .await
498 .expect("failed to create ramdisk");
499 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
500 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
501 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
502 }
503
504 #[fuchsia::test]
505 async fn create_with_guid_get_dir_proxy_destroy() {
506 let ramdisk = RamdiskClient::builder(512, 2048)
507 .guid(TEST_GUID)
508 .build()
509 .await
510 .expect("failed to create ramdisk");
511 let ramdisk_dir = ramdisk.as_dir().expect("invalid directory proxy");
512 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
513 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
514 }
515
516 #[fuchsia::test]
517 async fn create_open_destroy() {
518 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
519 let client = ramdisk.open().unwrap().into_proxy();
520 client.get_info().await.expect("get_info failed").unwrap();
521 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
522 }
524
525 #[fuchsia::test]
526 async fn create_open_forget() {
527 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
528 let client = ramdisk.open().unwrap().into_proxy();
529 client.get_info().await.expect("get_info failed").unwrap();
530 assert!(ramdisk.forget().is_ok());
531 client.get_info().await.expect("get_info failed").unwrap();
533 }
534
535 #[fuchsia::test]
536 async fn destroy_and_wait_for_removal() {
537 let mut ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
538 let dir = ramdisk.take_dir().unwrap();
539
540 assert_matches!(
541 fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(),
542 [
543 fuchsia_fs::directory::DirEntry {
544 name: name1,
545 kind: fuchsia_fs::directory::DirentKind::File,
546 },
547 fuchsia_fs::directory::DirEntry {
548 name: name2,
549 kind: fuchsia_fs::directory::DirentKind::File,
550 },
551 fuchsia_fs::directory::DirEntry {
552 name: name3,
553 kind: fuchsia_fs::directory::DirentKind::File,
554 },
555 ] if [name1, name2, name3] == [
556 fidl_fuchsia_device_fs::DEVICE_CONTROLLER_NAME,
557 fidl_fuchsia_device_fs::DEVICE_PROTOCOL_NAME,
558 fidl_fuchsia_device_fs::DEVICE_TOPOLOGY_NAME,
559 ]
560 );
561
562 let () = ramdisk.destroy_and_wait_for_removal().await.unwrap();
563
564 assert_matches!(fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(), []);
565 }
566}