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