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