fxfs_platform_testing/fuchsia/
testing.rs1use crate::fuchsia::directory::FxDirectory;
6use crate::fuchsia::file::FxFile;
7use crate::fuchsia::fxblob::BlobDirectory;
8use crate::fuchsia::memory_pressure::MemoryPressureMonitor;
9use crate::fuchsia::pager::PagerBacked;
10use crate::fuchsia::volume::{FxVolumeAndRoot, MemoryPressureConfig};
11use crate::fuchsia::volumes_directory::VolumesDirectory;
12use anyhow::{Context, Error};
13use fidl::endpoints::create_proxy;
14use fidl_fuchsia_io as fio;
15use fidl_fuchsia_memorypressure::WatcherProxy;
16use fuchsia_sync::Mutex;
17use fxfs::filesystem::{FxFilesystem, FxFilesystemBuilder, OpenFxFilesystem};
18use fxfs::fsck::errors::FsckIssue;
19use fxfs::fsck::{FsckOptions, fsck_volume_with_options, fsck_with_options};
20pub use fxfs::hooks::Hooks;
21use fxfs::hooks::HooksHandle;
22use fxfs::object_store::volume::root_volume;
23use fxfs::object_store::{NewChildStoreOptions, StoreOptions};
24use fxfs_crypt_common::CryptBase;
25use fxfs_crypto::Crypt;
26use fxfs_insecure_crypto::new_insecure_crypt;
27use refaults_vmo::PageFaultCounter;
28use std::sync::{Arc, Weak};
29use storage_device::DeviceHolder;
30use storage_device::fake_device::FakeDevice;
31use vfs::temp_clone::unblock;
32use zx::{self as zx, Status};
33
34struct State {
35 filesystem: OpenFxFilesystem,
36 volume: FxVolumeAndRoot,
37 volume_out_dir: fio::DirectoryProxy,
38 root: fio::DirectoryProxy,
39 volumes_directory: Arc<VolumesDirectory>,
40 mem_pressure_proxy: WatcherProxy,
41}
42
43pub struct TestFixture {
44 state: Option<State>,
45 encrypted: Option<Arc<CryptBase>>,
46}
47
48pub struct TestFixtureOptions {
49 pub encrypted: bool,
50 pub as_blob: bool,
51 pub format: bool,
52 pub hooks: Option<Arc<HooksHandle>>,
53 pub allow_type3_blobs: bool,
54 pub use_driver_blob_paging: bool,
55}
56
57impl Default for TestFixtureOptions {
58 fn default() -> Self {
59 Self {
60 encrypted: true,
61 as_blob: false,
62 format: true,
63 hooks: None,
64 allow_type3_blobs: false,
65 use_driver_blob_paging: false,
66 }
67 }
68}
69
70fn ensure_unique_or_poison(holder: DeviceHolder) -> DeviceHolder {
71 if Arc::strong_count(&*holder) > 1 {
72 if (*holder).poison().is_err() {
76 panic!("Remaining reference to device that doesn't support poison.");
78 };
79
80 std::mem::drop(holder);
83
84 std::thread::sleep(std::time::Duration::from_secs(5));
87 panic!("Timed out waiting for poison to trigger.");
88 }
89 holder
90}
91
92impl TestFixture {
93 pub async fn new() -> Self {
94 Self::open(DeviceHolder::new(FakeDevice::new(16384, 512)), TestFixtureOptions::default())
95 .await
96 }
97
98 pub async fn new_with_device(device: DeviceHolder) -> Self {
99 Self::open(device, TestFixtureOptions { format: false, ..Default::default() }).await
100 }
101
102 pub async fn new_unencrypted() -> Self {
103 Self::open(
104 DeviceHolder::new(FakeDevice::new(16384, 512)),
105 TestFixtureOptions { encrypted: false, ..Default::default() },
106 )
107 .await
108 }
109
110 pub async fn open(device: DeviceHolder, options: TestFixtureOptions) -> Self {
111 let crypt: Arc<CryptBase> = Arc::new(new_insecure_crypt());
112 let (mem_pressure_proxy, watcher_server) = create_proxy();
113 let mem_pressure = MemoryPressureMonitor::try_from(watcher_server)
114 .expect("Failed to create MemoryPressureMonitor");
115
116 let page_fault_counter =
117 Arc::new(PageFaultCounter::new().expect("Failed to create PageFaultCounter"));
118 let volume_name = if options.as_blob { "blob" } else { "vol" };
119 let (filesystem, volume, volumes_directory) = if options.format {
120 let mut builder = FxFilesystemBuilder::new()
121 .format(true)
122 .allow_type3_blobs(options.allow_type3_blobs);
123 if let Some(hooks) = options.hooks {
124 builder = builder.hooks(hooks);
125 }
126 let filesystem = builder.open(device).await.unwrap();
127 let root_volume = root_volume(filesystem.clone()).await.unwrap();
128 let store = root_volume
129 .new_volume(
130 volume_name,
131 NewChildStoreOptions {
132 options: StoreOptions {
133 crypt: if options.encrypted { Some(crypt.clone()) } else { None },
134 ..StoreOptions::default()
135 },
136 ..NewChildStoreOptions::default()
137 },
138 )
139 .await
140 .unwrap();
141 let store_object_id = store.store_object_id();
142
143 let volumes_directory = VolumesDirectory::new(
144 root_volume,
145 Weak::new(),
146 Some(mem_pressure),
147 page_fault_counter.clone(),
148 MemoryPressureConfig::default(),
149 )
150 .await
151 .unwrap();
152 let vol = if options.as_blob {
153 FxVolumeAndRoot::new::<BlobDirectory>(
154 Arc::downgrade(&volumes_directory),
155 store,
156 store_object_id,
157 volume_name.to_owned(),
158 page_fault_counter.clone(),
159 *volumes_directory.memory_pressure_config(),
160 )
161 .await
162 .unwrap()
163 } else {
164 FxVolumeAndRoot::new::<FxDirectory>(
165 Arc::downgrade(&volumes_directory),
166 store,
167 store_object_id,
168 volume_name.to_owned(),
169 page_fault_counter.clone(),
170 *volumes_directory.memory_pressure_config(),
171 )
172 .await
173 .unwrap()
174 };
175 (filesystem, vol, volumes_directory)
176 } else {
177 let filesystem = FxFilesystemBuilder::new()
178 .allow_type3_blobs(options.allow_type3_blobs)
179 .open(device)
180 .await
181 .unwrap();
182 let root_volume = root_volume(filesystem.clone()).await.unwrap();
183 let store = root_volume
184 .volume(
185 volume_name,
186 StoreOptions {
187 crypt: if options.encrypted { Some(crypt.clone()) } else { None },
188 ..StoreOptions::default()
189 },
190 )
191 .await
192 .unwrap();
193 let store_object_id = store.store_object_id();
194 let volumes_directory = VolumesDirectory::new(
195 root_volume,
196 Weak::new(),
197 Some(mem_pressure),
198 page_fault_counter.clone(),
199 MemoryPressureConfig::default(),
200 )
201 .await
202 .unwrap();
203 let vol = if options.as_blob {
204 FxVolumeAndRoot::new::<BlobDirectory>(
205 Arc::downgrade(&volumes_directory),
206 store,
207 store_object_id,
208 volume_name.to_owned(),
209 page_fault_counter.clone(),
210 *volumes_directory.memory_pressure_config(),
211 )
212 .await
213 .unwrap()
214 } else {
215 FxVolumeAndRoot::new::<FxDirectory>(
216 Arc::downgrade(&volumes_directory),
217 store,
218 store_object_id,
219 volume_name.to_owned(),
220 page_fault_counter.clone(),
221 *volumes_directory.memory_pressure_config(),
222 )
223 .await
224 .unwrap()
225 };
226
227 (filesystem, vol, volumes_directory)
228 };
229
230 if options.as_blob && options.use_driver_blob_paging {
231 volume.volume().set_use_driver_blob_paging(true);
232 }
233
234 let (root, server_end) = create_proxy::<fio::DirectoryMarker>();
235 volume.root().clone().serve(fio::PERM_READABLE | fio::PERM_WRITABLE, server_end);
236
237 let (volume_out_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
238 volumes_directory.lock().await.add_mount(volume_name, &volume);
239 volumes_directory
240 .serve_volume(&volume, server_end, options.as_blob)
241 .expect("serve_volume failed");
242
243 let encrypted = if options.encrypted { Some(crypt.clone()) } else { None };
244 Self {
245 state: Some(State {
246 filesystem,
247 volume,
248 volume_out_dir,
249 root,
250 volumes_directory,
251 mem_pressure_proxy,
252 }),
253 encrypted,
254 }
255 }
256
257 pub async fn close(mut self) -> DeviceHolder {
265 let State { filesystem, volume, volume_out_dir, root, volumes_directory, .. } =
266 std::mem::take(&mut self.state).unwrap();
267 volume_out_dir
268 .close()
269 .await
270 .expect("FIDL call failed")
271 .map_err(Status::err_from_raw)
272 .expect("close out_dir failed");
273 root.close()
276 .await
277 .expect("FIDL call failed")
278 .map_err(Status::err_from_raw)
279 .expect("close root failed");
280
281 volumes_directory.terminate().await;
285 drop(volumes_directory);
286
287 let store_id = volume.volume().store().store_object_id();
288
289 if volume.into_volume().try_unwrap().is_none() {
290 log::error!("References to volume still exist; hanging");
291 let () = std::future::pending().await;
292 }
293
294 filesystem.close().await.expect("close filesystem failed");
298 let device = ensure_unique_or_poison(filesystem.take_device().await);
299 device.reopen(false);
300 let filesystem = FxFilesystem::open(device).await.expect("open failed");
301 let options = FsckOptions {
302 fail_on_warning: true,
303 on_error: Box::new(|err: &FsckIssue| {
304 eprintln!("Fsck error: {:?}", err);
305 }),
306 ..Default::default()
307 };
308 fsck_with_options(filesystem.clone(), &options).await.expect("fsck failed");
309 let encrypted = if let Some(crypt) = &self.encrypted {
310 Some(crypt.clone() as Arc<dyn Crypt>)
311 } else {
312 None
313 };
314 fsck_volume_with_options(filesystem.as_ref(), &options, store_id, encrypted)
315 .await
316 .expect("fsck_volume failed");
317
318 filesystem.close().await.expect("close filesystem failed");
319 let device = ensure_unique_or_poison(filesystem.take_device().await);
320 device.reopen(false);
321
322 device
323 }
324
325 pub fn root(&self) -> &fio::DirectoryProxy {
326 &self.state.as_ref().unwrap().root
327 }
328
329 pub fn crypt(&self) -> Option<Arc<CryptBase>> {
330 self.encrypted.clone()
331 }
332
333 pub fn fs(&self) -> &Arc<FxFilesystem> {
334 &self.state.as_ref().unwrap().filesystem
335 }
336
337 pub fn volume(&self) -> &FxVolumeAndRoot {
338 &self.state.as_ref().unwrap().volume
339 }
340
341 pub fn volumes_directory(&self) -> &Arc<VolumesDirectory> {
342 &self.state.as_ref().unwrap().volumes_directory
343 }
344
345 pub fn volume_out_dir(&self) -> &fio::DirectoryProxy {
346 &self.state.as_ref().unwrap().volume_out_dir
347 }
348
349 pub fn memory_pressure_proxy(&self) -> &WatcherProxy {
350 &self.state.as_ref().unwrap().mem_pressure_proxy
351 }
352}
353
354pub struct TestCallback(Mutex<Option<Arc<dyn Fn() + Send + Sync>>>);
355pub struct TestCallbackGuard(&'static TestCallback);
356
357impl Drop for TestCallbackGuard {
358 fn drop(&mut self) {
359 *self.0.0.lock() = None;
360 }
361}
362
363impl TestCallback {
364 pub const fn new() -> Self {
365 Self(Mutex::new(None))
366 }
367
368 pub fn set<F>(&'static self, callback: F) -> TestCallbackGuard
370 where
371 F: Fn() + Send + Sync + 'static,
372 {
373 let arc: Arc<dyn Fn() + Send + Sync> = Arc::new(callback);
374 {
375 let mut inner = self.0.lock();
376 assert!(inner.is_none(), "Resetting TestCallback without dropping old guard.");
377 *inner = Some(arc.clone());
378 }
379 TestCallbackGuard(&self)
380 }
381
382 pub fn call(&self) {
383 let cb = self.0.lock().as_ref().map(|cb| cb.clone());
384 if let Some(cb) = cb {
388 cb();
389 }
390 }
391}
392
393impl Drop for TestFixture {
394 fn drop(&mut self) {
395 assert!(self.state.is_none(), "Did you forget to call TestFixture::close?");
396 }
397}
398
399pub async fn close_file_checked(file: fio::FileProxy) {
400 file.sync()
401 .await
402 .expect("FIDL call failed")
403 .map_err(Status::err_from_raw)
404 .expect("sync failed");
405 file.close()
406 .await
407 .expect("FIDL call failed")
408 .map_err(Status::err_from_raw)
409 .expect("close failed");
410}
411
412pub async fn close_dir_checked(dir: fio::DirectoryProxy) {
413 dir.close()
414 .await
415 .expect("FIDL call failed")
416 .map_err(Status::err_from_raw)
417 .expect("close failed");
418}
419
420pub async fn open_file(
422 dir: &fio::DirectoryProxy,
423 path: &str,
424 flags: fio::Flags,
425 options: &fio::Options,
426) -> Result<fio::FileProxy, Error> {
427 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
428 dir.open(path, flags | fio::Flags::PROTOCOL_FILE, options, server_end.into_channel())?;
429 let _: Vec<_> = proxy.query().await?;
430 Ok(proxy)
431}
432
433pub async fn open_file_checked(
435 dir: &fio::DirectoryProxy,
436 path: &str,
437 flags: fio::Flags,
438 options: &fio::Options,
439) -> fio::FileProxy {
440 open_file(dir, path, flags, options).await.expect("open_file failed")
441}
442
443pub async fn open_dir(
445 dir: &fio::DirectoryProxy,
446 path: &str,
447 flags: fio::Flags,
448 options: &fio::Options,
449) -> Result<fio::DirectoryProxy, Error> {
450 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
451 dir.open(path, flags | fio::Flags::PROTOCOL_DIRECTORY, options, server_end.into_channel())?;
452 let _: Vec<_> = proxy.query().await?;
453 Ok(proxy)
454}
455
456pub async fn open_dir_checked(
458 dir: &fio::DirectoryProxy,
459 path: &str,
460 flags: fio::Flags,
461 options: fio::Options,
462) -> fio::DirectoryProxy {
463 open_dir(dir, path, flags, &options).await.expect("open_dir failed")
464}
465
466pub async fn write_at(file: &FxFile, offset: u64, content: &[u8]) -> Result<usize, Error> {
468 let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, file.vmo(), 0)
469 .context("stream create failed")?;
470 let content = content.to_vec();
471 unblock(move || {
472 stream
473 .write_at(zx::StreamWriteOptions::empty(), offset, &content)
474 .context("stream write failed")
475 })
476 .await
477}