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 device_flags: Option<fhardware_block::Flag>,
34
35 publish: bool,
38}
39
40enum RamdiskSource {
41 Vmo { vmo: zx::Vmo },
42 Size { block_count: u64 },
43}
44
45impl RamdiskClientBuilder {
46 pub fn new(block_size: u64, block_count: u64) -> Self {
48 Self {
49 ramdisk_source: RamdiskSource::Size { block_count },
50 block_size,
51 max_transfer_blocks: None,
52 guid: None,
53 dev_root: None,
54 use_v2: false,
55 ramdisk_service: None,
56 publish: false,
57 device_flags: None,
58 }
59 }
60
61 pub fn new_with_vmo(vmo: zx::Vmo, block_size: Option<u64>) -> Self {
63 Self {
64 ramdisk_source: RamdiskSource::Vmo { vmo },
65 block_size: block_size.unwrap_or(0),
66 max_transfer_blocks: None,
67 guid: None,
68 dev_root: None,
69 use_v2: false,
70 ramdisk_service: None,
71 publish: false,
72 device_flags: None,
73 }
74 }
75
76 pub fn dev_root(mut self, dev_root: fio::DirectoryProxy) -> Self {
78 self.dev_root = Some(dev_root);
79 self
80 }
81
82 pub fn guid(mut self, guid: [u8; GUID_LEN]) -> Self {
84 self.guid = Some(guid);
85 self
86 }
87
88 pub fn max_transfer_blocks(mut self, value: u32) -> Self {
90 self.max_transfer_blocks = Some(value);
91 self
92 }
93
94 pub fn use_v2(mut self) -> Self {
96 self.use_v2 = true;
97 self
98 }
99
100 pub fn ramdisk_service(mut self, service: fio::DirectoryProxy) -> Self {
102 self.ramdisk_service = Some(service);
103 self
104 }
105
106 pub fn publish(mut self) -> Self {
108 self.publish = true;
109 self
110 }
111
112 pub fn device_flags(mut self, device_flags: fhardware_block::Flag) -> Self {
114 self.device_flags = Some(device_flags);
115 self
116 }
117
118 pub async fn build(self) -> Result<RamdiskClient, Error> {
120 let Self {
121 ramdisk_source,
122 block_size,
123 max_transfer_blocks,
124 guid,
125 dev_root,
126 use_v2,
127 ramdisk_service,
128 publish,
129 device_flags,
130 } = self;
131
132 if use_v2 {
133 let service = match ramdisk_service {
135 Some(s) => {
136 Service::from_service_dir_proxy(s, fidl_fuchsia_hardware_ramdisk::ServiceMarker)
137 }
138 None => Service::open(fidl_fuchsia_hardware_ramdisk::ServiceMarker)?,
139 };
140 let ramdisk_controller = service.watch_for_any().await?.connect_to_controller()?;
141
142 let type_guid = guid.map(|guid| Guid { value: guid });
143
144 let options = match ramdisk_source {
145 RamdiskSource::Vmo { vmo } => framdisk::Options {
146 vmo: Some(vmo),
147 block_size: if block_size == 0 {
148 None
149 } else {
150 Some(block_size.try_into().unwrap())
151 },
152 type_guid,
153 publish: Some(publish),
154 max_transfer_blocks,
155 device_flags,
156 ..Default::default()
157 },
158 RamdiskSource::Size { block_count } => framdisk::Options {
159 block_count: Some(block_count),
160 block_size: Some(block_size.try_into().unwrap()),
161 type_guid,
162 publish: Some(publish),
163 max_transfer_blocks,
164 device_flags,
165 ..Default::default()
166 },
167 };
168
169 let (outgoing, event) =
170 ramdisk_controller.create(options).await?.map_err(|s| zx::Status::from_raw(s))?;
171
172 RamdiskClient::new_v2(outgoing.into_proxy(), event)
173 } else {
174 let dev_root = if let Some(dev_root) = dev_root {
175 dev_root
176 } else {
177 fuchsia_fs::directory::open_in_namespace(DEV_PATH, fio::PERM_READABLE)
178 .with_context(|| format!("open {}", DEV_PATH))?
179 };
180 let ramdisk_controller = device_watcher::recursive_wait_and_open::<
181 RamdiskControllerMarker,
182 >(&dev_root, RAMCTL_PATH)
183 .await
184 .with_context(|| format!("waiting for {}", RAMCTL_PATH))?;
185 let type_guid = guid.map(|guid| Guid { value: guid });
186 let name = match ramdisk_source {
187 RamdiskSource::Vmo { vmo } => ramdisk_controller
188 .create_from_vmo_with_params(vmo, block_size, type_guid.as_ref())
189 .await?
190 .map_err(zx::Status::from_raw)
191 .context("creating ramdisk from vmo")?,
192 RamdiskSource::Size { block_count } => ramdisk_controller
193 .create(block_size, block_count, type_guid.as_ref())
194 .await?
195 .map_err(zx::Status::from_raw)
196 .with_context(|| format!("creating ramdisk with {} blocks", block_count))?,
197 };
198 let name = name.ok_or_else(|| anyhow!("Failed to get instance name"))?;
199 RamdiskClient::new(dev_root, &name).await
200 }
201 }
202}
203
204pub enum RamdiskClient {
208 V1 {
210 block_dir: fio::DirectoryProxy,
212
213 block_controller: ControllerProxy,
215
216 ramdisk_controller: Option<ControllerProxy>,
218 },
219 V2 {
221 outgoing: fio::DirectoryProxy,
223
224 _event: zx::EventPair,
226 },
227}
228
229impl RamdiskClient {
230 async fn new(dev_root: fio::DirectoryProxy, instance_name: &str) -> Result<Self, Error> {
231 let ramdisk_path = format!("{RAMCTL_PATH}/{instance_name}");
232 let ramdisk_controller_path = format!("{ramdisk_path}/device_controller");
233 let block_path = format!("{ramdisk_path}/{BLOCK_EXTENSION}");
234
235 let ramdisk_controller = device_watcher::recursive_wait_and_open::<ControllerMarker>(
237 &dev_root,
238 &ramdisk_controller_path,
239 )
240 .await
241 .with_context(|| format!("waiting for {}", &ramdisk_controller_path))?;
242
243 let block_dir = device_watcher::recursive_wait_and_open_directory(&dev_root, &block_path)
245 .await
246 .with_context(|| format!("waiting for {}", &block_path))?;
247
248 let block_controller = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
249 &block_dir,
250 "device_controller",
251 )
252 .with_context(|| {
253 format!("opening block controller at {}/device_controller", &block_path)
254 })?;
255
256 Ok(Self::V1 { block_dir, block_controller, ramdisk_controller: Some(ramdisk_controller) })
257 }
258
259 fn new_v2(outgoing: fio::DirectoryProxy, event: zx::EventPair) -> Result<Self, Error> {
260 Ok(Self::V2 { outgoing, _event: event })
261 }
262
263 pub fn builder(block_size: u64, block_count: u64) -> RamdiskClientBuilder {
265 RamdiskClientBuilder::new(block_size, block_count)
266 }
267
268 pub async fn create(block_size: u64, block_count: u64) -> Result<Self, Error> {
270 Self::builder(block_size, block_count).build().await
271 }
272
273 pub fn as_controller(&self) -> Option<&ControllerProxy> {
275 match self {
276 Self::V1 { block_controller, .. } => Some(block_controller),
277 Self::V2 { .. } => None,
278 }
279 }
280
281 pub fn as_dir(&self) -> Option<&fio::DirectoryProxy> {
283 match self {
284 Self::V1 { block_dir, .. } => Some(block_dir),
285 Self::V2 { .. } => None,
286 }
287 }
288
289 pub fn open(&self) -> Result<fidl::endpoints::ClientEnd<fhardware_block::BlockMarker>, Error> {
291 let (client, server_end) = fidl::endpoints::create_endpoints();
292 self.connect(server_end)?;
293 Ok(client)
294 }
295
296 pub fn connector(&self) -> Result<Box<dyn BlockConnector>, Error> {
298 match self {
299 Self::V1 { .. } => {
300 let block_dir = fuchsia_fs::directory::clone(
307 self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?,
308 )?;
309 Ok(Box::new(DirBasedBlockConnector::new(block_dir, ".".to_string())))
310 }
311 Self::V2 { outgoing, .. } => {
312 let block_dir = fuchsia_fs::directory::clone(outgoing)?;
313 Ok(Box::new(DirBasedBlockConnector::new(
314 block_dir,
315 format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
316 )))
317 }
318 }
319 }
320
321 pub fn connect(
323 &self,
324 server_end: fidl::endpoints::ServerEnd<fhardware_block::BlockMarker>,
325 ) -> Result<(), Error> {
326 match self {
327 Self::V1 { .. } => {
328 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
329 Ok(block_dir.open(
330 ".",
331 fio::Flags::empty(),
332 &fio::Options::default(),
333 server_end.into_channel(),
334 )?)
335 }
336 Self::V2 { outgoing, .. } => Ok(outgoing.open(
337 &format!("svc/{}", fvolume::VolumeMarker::PROTOCOL_NAME),
338 fio::Flags::empty(),
339 &fio::Options::default(),
340 server_end.into_channel(),
341 )?),
342 }
343 }
344
345 pub fn open_controller(&self) -> Result<ControllerProxy, Error> {
347 match self {
348 Self::V1 { .. } => {
349 let block_dir = self.as_dir().ok_or_else(|| anyhow!("directory is invalid"))?;
350 let controller_proxy = connect_to_named_protocol_at_dir_root::<ControllerMarker>(
351 block_dir,
352 "device_controller",
353 )
354 .context("opening block controller")?;
355 Ok(controller_proxy)
356 }
357 Self::V2 { .. } => Err(anyhow!("Not supported")),
358 }
359 }
360
361 pub fn open_ramdisk(&self) -> Result<framdisk::RamdiskProxy, Error> {
363 match self {
364 Self::V1 { .. } => Err(anyhow!("Not supported")),
365 Self::V2 { outgoing, .. } => {
366 let (client, server) = fidl::endpoints::create_proxy::<framdisk::RamdiskMarker>();
367 outgoing.open(
368 &format!("svc/{}", framdisk::RamdiskMarker::PROTOCOL_NAME),
369 fio::Flags::empty(),
370 &fio::Options::default(),
371 server.into_channel(),
372 )?;
373 Ok(client)
374 }
375 }
376 }
377
378 pub async fn destroy(mut self) -> Result<(), Error> {
382 match &mut self {
383 Self::V1 { ramdisk_controller, .. } => {
384 let ramdisk_controller = ramdisk_controller
385 .take()
386 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
387 let () = ramdisk_controller
388 .schedule_unbind()
389 .await
390 .context("unbind transport")?
391 .map_err(zx::Status::from_raw)
392 .context("unbind response")?;
393 }
394 Self::V2 { .. } => {} }
396 Ok(())
397 }
398
399 pub async fn destroy_and_wait_for_removal(mut self) -> Result<(), Error> {
403 match &mut self {
404 Self::V1 { block_controller, ramdisk_controller, .. } => {
405 let ramdisk_controller = ramdisk_controller
412 .take()
413 .ok_or_else(|| anyhow!("ramdisk controller is invalid"))?;
414 let () = ramdisk_controller
415 .schedule_unbind()
416 .await
417 .context("unbind transport")?
418 .map_err(zx::Status::from_raw)
419 .context("unbind response")?;
420 let _: (zx::Signals, zx::Signals) = futures::future::try_join(
421 block_controller.on_closed(),
422 ramdisk_controller.on_closed(),
423 )
424 .await
425 .context("on closed")?;
426 }
427 Self::V2 { .. } => {}
428 }
429 Ok(())
430 }
431
432 pub fn forget(mut self) -> Result<(), Error> {
437 match &mut self {
438 Self::V1 { ramdisk_controller, .. } => {
439 let _ = ramdisk_controller.take();
440 Ok(())
441 }
442 Self::V2 { .. } => Err(anyhow!("Not supported")),
443 }
444 }
445}
446
447impl BlockConnector for RamdiskClient {
448 fn connect_channel_to_volume(
449 &self,
450 server_end: fidl::endpoints::ServerEnd<fidl_fuchsia_hardware_block_volume::VolumeMarker>,
451 ) -> Result<(), Error> {
452 self.connect(server_end.into_channel().into())
453 }
454}
455
456impl Drop for RamdiskClient {
457 fn drop(&mut self) {
458 if let Self::V1 { ramdisk_controller, .. } = self {
459 if let Some(ramdisk_controller) = ramdisk_controller.take() {
460 let _: Result<Result<(), _>, _> = ControllerSynchronousProxy::new(
461 ramdisk_controller.into_channel().unwrap().into(),
462 )
463 .schedule_unbind(zx::MonotonicInstant::INFINITE);
464 }
465 }
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use super::*;
472 use assert_matches::assert_matches;
473
474 const TEST_GUID: [u8; GUID_LEN] = [
477 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
478 0x10,
479 ];
480
481 #[fuchsia::test]
482 async fn create_get_dir_proxy_destroy() {
483 let ramdisk =
485 RamdiskClient::builder(512, 2048).build().await.expect("failed to create ramdisk");
486 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
487 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
488 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
489 }
490
491 #[fuchsia::test]
492 async fn create_with_dev_root_and_guid_get_dir_proxy_destroy() {
493 let dev_root = fuchsia_fs::directory::open_in_namespace(DEV_PATH, fio::PERM_READABLE)
494 .with_context(|| format!("open {}", DEV_PATH))
495 .expect("failed to create directory proxy");
496 let ramdisk = RamdiskClient::builder(512, 2048)
497 .dev_root(dev_root)
498 .guid(TEST_GUID)
499 .build()
500 .await
501 .expect("failed to create ramdisk");
502 let ramdisk_dir = ramdisk.as_dir().expect("directory is invalid");
503 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
504 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
505 }
506
507 #[fuchsia::test]
508 async fn create_with_guid_get_dir_proxy_destroy() {
509 let ramdisk = RamdiskClient::builder(512, 2048)
510 .guid(TEST_GUID)
511 .build()
512 .await
513 .expect("failed to create ramdisk");
514 let ramdisk_dir = ramdisk.as_dir().expect("invalid directory proxy");
515 fuchsia_fs::directory::readdir(ramdisk_dir).await.expect("failed to readdir");
516 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
517 }
518
519 #[fuchsia::test]
520 async fn create_open_destroy() {
521 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
522 let client = ramdisk.open().unwrap().into_proxy();
523 client.get_info().await.expect("get_info failed").unwrap();
524 ramdisk.destroy().await.expect("failed to destroy the ramdisk");
525 }
527
528 #[fuchsia::test]
529 async fn create_open_forget() {
530 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
531 let client = ramdisk.open().unwrap().into_proxy();
532 client.get_info().await.expect("get_info failed").unwrap();
533 assert!(ramdisk.forget().is_ok());
534 client.get_info().await.expect("get_info failed").unwrap();
536 }
537
538 #[fuchsia::test]
539 async fn destroy_and_wait_for_removal() {
540 let ramdisk = RamdiskClient::create(512, 2048).await.unwrap();
541 let dir = fuchsia_fs::directory::clone(ramdisk.as_dir().unwrap()).unwrap();
542
543 assert_matches!(
544 fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(),
545 [
546 fuchsia_fs::directory::DirEntry {
547 name: name1,
548 kind: fuchsia_fs::directory::DirentKind::File,
549 },
550 fuchsia_fs::directory::DirEntry {
551 name: name2,
552 kind: fuchsia_fs::directory::DirentKind::File,
553 },
554 fuchsia_fs::directory::DirEntry {
555 name: name3,
556 kind: fuchsia_fs::directory::DirentKind::File,
557 },
558 ] if [name1, name2, name3] == [
559 fidl_fuchsia_device_fs::DEVICE_CONTROLLER_NAME,
560 fidl_fuchsia_device_fs::DEVICE_PROTOCOL_NAME,
561 fidl_fuchsia_device_fs::DEVICE_TOPOLOGY_NAME,
562 ]
563 );
564
565 let () = ramdisk.destroy_and_wait_for_removal().await.unwrap();
566
567 assert_matches!(fuchsia_fs::directory::readdir(&dir).await.unwrap().as_slice(), []);
568 }
569}