1#![deny(missing_docs)]
8
9use anyhow::{anyhow, Context as _, Result};
10use async_trait::async_trait;
11use fidl::endpoints::create_proxy;
12use fidl::HandleBased as _;
13use fidl_fuchsia_blackout_test::{ControllerRequest, ControllerRequestStream};
14use fidl_fuchsia_device::ControllerMarker;
15use fidl_fuchsia_hardware_block_volume::VolumeManagerMarker;
16use fs_management::filesystem::BlockConnector;
17use fs_management::format::DiskFormat;
18use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_at_path, Service};
19use fuchsia_component::server::{ServiceFs, ServiceObj};
20use fuchsia_fs::directory::readdir;
21use futures::{future, FutureExt, StreamExt, TryFutureExt, TryStreamExt};
22use rand::rngs::StdRng;
23use rand::{distributions, Rng, SeedableRng};
24use std::pin::pin;
25use std::sync::Arc;
26use storage_isolated_driver_manager::{
27 create_random_guid, find_block_device, find_block_device_devfs, into_guid,
28 wait_for_block_device_devfs, BlockDeviceMatcher, Guid,
29};
30use {
31 fidl_fuchsia_io as fio, fidl_fuchsia_storage_partitions as fpartitions, fuchsia_async as fasync,
32};
33
34pub mod random_op;
35pub mod static_tree;
36
37#[async_trait]
39pub trait Test {
40 async fn setup(
42 self: Arc<Self>,
43 device_label: String,
44 device_path: Option<String>,
45 seed: u64,
46 ) -> Result<()>;
47 async fn test(
49 self: Arc<Self>,
50 device_label: String,
51 device_path: Option<String>,
52 seed: u64,
53 ) -> Result<()>;
54 async fn verify(
56 self: Arc<Self>,
57 device_label: String,
58 device_path: Option<String>,
59 seed: u64,
60 ) -> Result<()>;
61}
62
63struct BlackoutController(ControllerRequestStream);
64
65pub struct TestServer<'a, T> {
67 fs: ServiceFs<ServiceObj<'a, BlackoutController>>,
68 test: Arc<T>,
69}
70
71impl<'a, T> TestServer<'a, T>
72where
73 T: Test + 'static,
74{
75 pub fn new(test: T) -> Result<TestServer<'a, T>> {
77 let mut fs = ServiceFs::new();
78 fs.dir("svc").add_fidl_service(BlackoutController);
79 fs.take_and_serve_directory_handle()?;
80
81 Ok(TestServer { fs, test: Arc::new(test) })
82 }
83
84 pub async fn serve(self) {
86 const MAX_CONCURRENT: usize = 10_000;
87 let test = self.test;
88 self.fs
89 .for_each_concurrent(MAX_CONCURRENT, move |stream| {
90 handle_request(test.clone(), stream).unwrap_or_else(|e| log::error!("{}", e))
91 })
92 .await;
93 }
94}
95
96async fn handle_request<T: Test + 'static>(
97 test: Arc<T>,
98 BlackoutController(mut stream): BlackoutController,
99) -> Result<()> {
100 while let Some(request) = stream.try_next().await? {
101 handle_controller(test.clone(), request).await?;
102 }
103
104 Ok(())
105}
106
107async fn handle_controller<T: Test + 'static>(
108 test: Arc<T>,
109 request: ControllerRequest,
110) -> Result<()> {
111 match request {
112 ControllerRequest::Setup { responder, device_label, device_path, seed } => {
113 let res = test.setup(device_label, device_path, seed).await.map_err(|err| {
114 log::error!(err:?; "Setup failed");
115 zx::Status::INTERNAL.into_raw()
116 });
117 responder.send(res)?;
118 }
119 ControllerRequest::Test { responder, device_label, device_path, seed, duration } => {
120 let test_fut = test.test(device_label, device_path, seed).map_err(|err| {
121 log::error!(err:?; "Test failed");
122 zx::Status::INTERNAL.into_raw()
123 });
124 if duration != 0 {
125 log::info!("starting test and replying in {} seconds...", duration);
128 let timer = pin!(fasync::Timer::new(std::time::Duration::from_secs(duration)));
129 let res = match future::select(test_fut, timer).await {
130 future::Either::Left((res, _)) => res,
131 future::Either::Right((_, test_fut)) => {
132 fasync::Task::spawn(test_fut.map(|_| ())).detach();
133 Ok(())
134 }
135 };
136 responder.send(res)?;
137 } else {
138 log::info!("starting test...");
140 responder.send(test_fut.await)?;
141 }
142 }
143 ControllerRequest::Verify { responder, device_label, device_path, seed } => {
144 let res = test.verify(device_label, device_path, seed).await.map_err(|e| {
145 log::warn!("{:?}", e);
147 zx::Status::BAD_STATE.into_raw()
148 });
149 responder.send(res)?;
150 }
151 }
152
153 Ok(())
154}
155
156pub fn generate_content(seed: u64) -> Vec<u8> {
158 let mut rng = StdRng::seed_from_u64(seed);
159
160 let size = rng.gen_range(1..1 << 16);
161 rng.sample_iter(&distributions::Standard).take(size).collect()
162}
163
164pub async fn find_dev(dev: &str) -> Result<String> {
167 let dev_class_block =
168 fuchsia_fs::directory::open_in_namespace("/dev/class/block", fio::PERM_READABLE)?;
169 for entry in readdir(&dev_class_block).await? {
170 let path = format!("/dev/class/block/{}", entry.name);
171 let proxy = connect_to_protocol_at_path::<ControllerMarker>(&path)?;
172 let topo_path = proxy.get_topological_path().await?.map_err(|s| zx::Status::from_raw(s))?;
173 log::info!("{} => {}", path, topo_path);
174 if dev == topo_path {
175 return Ok(path);
176 }
177 }
178 Err(anyhow::anyhow!("Couldn't find {} in /dev/class/block", dev))
179}
180
181pub fn dev() -> fio::DirectoryProxy {
183 fuchsia_fs::directory::open_in_namespace("/dev", fio::PERM_READABLE)
184 .expect("failed to open /dev")
185}
186
187const BLACKOUT_TYPE_GUID: &Guid = &[
190 0x68, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
191];
192
193const GPT_PARTITION_SIZE: u64 = 60 * 1024 * 1024;
194
195pub async fn set_up_partition(
200 device_label: String,
201 storage_host: bool,
202) -> Result<Box<dyn BlockConnector>> {
203 if !storage_host {
204 return set_up_partition_devfs(device_label).await;
205 }
206
207 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
208 let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
209
210 let service_instances =
211 partitions.clone().enumerate().await.expect("Failed to enumerate partitions");
212 if let Some(connector) =
213 find_block_device(&[BlockDeviceMatcher::Name(&device_label)], service_instances.into_iter())
214 .await
215 .context("Failed to find block device")?
216 {
217 log::info!(device_label:%; "found existing partition");
218 Ok(Box::new(connector))
219 } else {
220 log::info!(device_label:%; "adding new partition to the system gpt");
221 let info =
222 manager.get_block_info().await.expect("FIDL error").expect("get_block_info failed");
223 let transaction = manager
224 .create_transaction()
225 .await
226 .expect("FIDL error")
227 .map_err(zx::Status::from_raw)
228 .expect("create_transaction failed");
229 let request = fpartitions::PartitionsManagerAddPartitionRequest {
230 transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
231 name: Some(device_label.clone()),
232 type_guid: Some(into_guid(BLACKOUT_TYPE_GUID.clone())),
233 instance_guid: Some(into_guid(create_random_guid())),
234 num_blocks: Some(GPT_PARTITION_SIZE / info.1 as u64),
235 ..Default::default()
236 };
237 manager
238 .add_partition(request)
239 .await
240 .expect("FIDL error")
241 .map_err(zx::Status::from_raw)
242 .expect("add_partition failed");
243 manager
244 .commit_transaction(transaction)
245 .await
246 .expect("FIDL error")
247 .map_err(zx::Status::from_raw)
248 .expect("add_partition failed");
249 let service_instances =
250 partitions.enumerate().await.expect("Failed to enumerate partitions");
251 let connector = find_block_device(
252 &[BlockDeviceMatcher::Name(&device_label)],
253 service_instances.into_iter(),
254 )
255 .await
256 .context("Failed to find block device")?
257 .unwrap();
258 Ok(Box::new(connector))
259 }
260}
261
262async fn set_up_partition_devfs(device_label: String) -> Result<Box<dyn BlockConnector>> {
265 let mut partition_path = if let Ok(path) =
266 find_block_device_devfs(&[BlockDeviceMatcher::Name(&device_label)]).await
267 {
268 log::info!("found existing partition");
269 path
270 } else {
271 log::info!("finding existing gpt and adding a new partition to it");
272 let mut gpt_block_path =
273 find_block_device_devfs(&[BlockDeviceMatcher::ContentsMatch(DiskFormat::Gpt)])
274 .await
275 .context("finding gpt device failed")?;
276 gpt_block_path.push("device_controller");
277 let gpt_block_controller =
278 connect_to_protocol_at_path::<ControllerMarker>(gpt_block_path.to_str().unwrap())
279 .context("connecting to block controller")?;
280 let gpt_path = gpt_block_controller
281 .get_topological_path()
282 .await
283 .context("get_topo fidl error")?
284 .map_err(zx::Status::from_raw)
285 .context("get_topo failed")?;
286 let gpt_controller = connect_to_protocol_at_path::<ControllerMarker>(&format!(
287 "{}/gpt/device_controller",
288 gpt_path
289 ))
290 .context("connecting to gpt controller")?;
291
292 let (volume_manager, server) = create_proxy::<VolumeManagerMarker>();
293 gpt_controller
294 .connect_to_device_fidl(server.into_channel())
295 .context("connecting to gpt fidl")?;
296 let slice_size = {
297 let (status, info) = volume_manager.get_info().await.context("get_info fidl error")?;
298 zx::ok(status).context("get_info returned error")?;
299 info.unwrap().slice_size
300 };
301 let slice_count = GPT_PARTITION_SIZE / slice_size;
302 let instance_guid = into_guid(create_random_guid());
303 let status = volume_manager
304 .allocate_partition(
305 slice_count,
306 &into_guid(BLACKOUT_TYPE_GUID.clone()),
307 &instance_guid,
308 &device_label,
309 0,
310 )
311 .await
312 .context("allocating test partition fidl error")?;
313 zx::ok(status).context("allocating test partition returned error")?;
314
315 wait_for_block_device_devfs(&[
316 BlockDeviceMatcher::Name(&device_label),
317 BlockDeviceMatcher::TypeGuid(&BLACKOUT_TYPE_GUID),
318 ])
319 .await
320 .context("waiting for new gpt partition")?
321 };
322 partition_path.push("device_controller");
323 log::info!(partition_path:?; "found partition to use");
324 Ok(Box::new(
325 connect_to_protocol_at_path::<ControllerMarker>(partition_path.to_str().unwrap())
326 .context("connecting to provided path")?,
327 ))
328}
329
330pub async fn find_partition(
334 device_label: String,
335 storage_host: bool,
336) -> Result<Box<dyn BlockConnector>> {
337 if !storage_host {
338 return find_partition_devfs(device_label).await;
339 }
340
341 let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
342 let service_instances = partitions.enumerate().await.expect("Failed to enumerate partitions");
343 let connector = find_block_device(
344 &[BlockDeviceMatcher::Name(&device_label)],
345 service_instances.into_iter(),
346 )
347 .await
348 .context("Failed to find block device")?
349 .ok_or_else(|| anyhow!("Block device not found"))?;
350 log::info!(device_label:%; "found existing partition");
351 Ok(Box::new(connector))
352}
353
354async fn find_partition_devfs(device_label: String) -> Result<Box<dyn BlockConnector>> {
357 log::info!("finding gpt");
358 let mut partition_path = find_block_device_devfs(&[BlockDeviceMatcher::Name(&device_label)])
359 .await
360 .context("finding block device")?;
361 partition_path.push("device_controller");
362 log::info!(partition_path:?; "found partition to use");
363 Ok(Box::new(
364 connect_to_protocol_at_path::<ControllerMarker>(partition_path.to_str().unwrap())
365 .context("connecting to provided path")?,
366 ))
367}