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::PageRefaultCounter;
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}
55
56impl Default for TestFixtureOptions {
57 fn default() -> Self {
58 Self {
59 encrypted: true,
60 as_blob: false,
61 format: true,
62 hooks: None,
63 allow_type3_blobs: false,
64 }
65 }
66}
67
68fn ensure_unique_or_poison(holder: DeviceHolder) -> DeviceHolder {
69 if Arc::strong_count(&*holder) > 1 {
70 if (*holder).poison().is_err() {
74 panic!("Remaining reference to device that doesn't support poison.");
76 };
77
78 std::mem::drop(holder);
81
82 std::thread::sleep(std::time::Duration::from_secs(5));
85 panic!("Timed out waiting for poison to trigger.");
86 }
87 holder
88}
89
90impl TestFixture {
91 pub async fn new() -> Self {
92 Self::open(DeviceHolder::new(FakeDevice::new(16384, 512)), TestFixtureOptions::default())
93 .await
94 }
95
96 pub async fn new_with_device(device: DeviceHolder) -> Self {
97 Self::open(device, TestFixtureOptions { format: false, ..Default::default() }).await
98 }
99
100 pub async fn new_unencrypted() -> Self {
101 Self::open(
102 DeviceHolder::new(FakeDevice::new(16384, 512)),
103 TestFixtureOptions { encrypted: false, ..Default::default() },
104 )
105 .await
106 }
107
108 pub async fn open(device: DeviceHolder, options: TestFixtureOptions) -> Self {
109 let crypt: Arc<CryptBase> = Arc::new(new_insecure_crypt());
110 let (mem_pressure_proxy, watcher_server) = create_proxy();
111 let mem_pressure = MemoryPressureMonitor::try_from(watcher_server)
112 .expect("Failed to create MemoryPressureMonitor");
113
114 let blob_resupplied_count =
115 Arc::new(PageRefaultCounter::new().expect("Failed to create PageRefaultCounter"));
116 let volume_name = if options.as_blob { "blob" } else { "vol" };
117 let (filesystem, volume, volumes_directory) = if options.format {
118 let mut builder = FxFilesystemBuilder::new()
119 .format(true)
120 .allow_type3_blobs(options.allow_type3_blobs);
121 if let Some(hooks) = options.hooks {
122 builder = builder.hooks(hooks);
123 }
124 let filesystem = builder.open(device).await.unwrap();
125 let root_volume = root_volume(filesystem.clone()).await.unwrap();
126 let store = root_volume
127 .new_volume(
128 volume_name,
129 NewChildStoreOptions {
130 options: StoreOptions {
131 crypt: if options.encrypted { Some(crypt.clone()) } else { None },
132 ..StoreOptions::default()
133 },
134 ..NewChildStoreOptions::default()
135 },
136 )
137 .await
138 .unwrap();
139 let store_object_id = store.store_object_id();
140
141 let volumes_directory = VolumesDirectory::new(
142 root_volume,
143 Weak::new(),
144 Some(mem_pressure),
145 blob_resupplied_count.clone(),
146 MemoryPressureConfig::default(),
147 )
148 .await
149 .unwrap();
150 let vol = if options.as_blob {
151 FxVolumeAndRoot::new::<BlobDirectory>(
152 Arc::downgrade(&volumes_directory),
153 store,
154 store_object_id,
155 volume_name.to_owned(),
156 blob_resupplied_count.clone(),
157 *volumes_directory.memory_pressure_config(),
158 )
159 .await
160 .unwrap()
161 } else {
162 FxVolumeAndRoot::new::<FxDirectory>(
163 Arc::downgrade(&volumes_directory),
164 store,
165 store_object_id,
166 volume_name.to_owned(),
167 blob_resupplied_count.clone(),
168 *volumes_directory.memory_pressure_config(),
169 )
170 .await
171 .unwrap()
172 };
173 (filesystem, vol, volumes_directory)
174 } else {
175 let filesystem = FxFilesystemBuilder::new()
176 .allow_type3_blobs(options.allow_type3_blobs)
177 .open(device)
178 .await
179 .unwrap();
180 let root_volume = root_volume(filesystem.clone()).await.unwrap();
181 let store = root_volume
182 .volume(
183 volume_name,
184 StoreOptions {
185 crypt: if options.encrypted { Some(crypt.clone()) } else { None },
186 ..StoreOptions::default()
187 },
188 )
189 .await
190 .unwrap();
191 let store_object_id = store.store_object_id();
192 let volumes_directory = VolumesDirectory::new(
193 root_volume,
194 Weak::new(),
195 Some(mem_pressure),
196 blob_resupplied_count.clone(),
197 MemoryPressureConfig::default(),
198 )
199 .await
200 .unwrap();
201 let vol = if options.as_blob {
202 FxVolumeAndRoot::new::<BlobDirectory>(
203 Arc::downgrade(&volumes_directory),
204 store,
205 store_object_id,
206 volume_name.to_owned(),
207 blob_resupplied_count.clone(),
208 *volumes_directory.memory_pressure_config(),
209 )
210 .await
211 .unwrap()
212 } else {
213 FxVolumeAndRoot::new::<FxDirectory>(
214 Arc::downgrade(&volumes_directory),
215 store,
216 store_object_id,
217 volume_name.to_owned(),
218 blob_resupplied_count.clone(),
219 *volumes_directory.memory_pressure_config(),
220 )
221 .await
222 .unwrap()
223 };
224
225 (filesystem, vol, volumes_directory)
226 };
227
228 let (root, server_end) = create_proxy::<fio::DirectoryMarker>();
229 volume.root().clone().serve(fio::PERM_READABLE | fio::PERM_WRITABLE, server_end);
230
231 let (volume_out_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
232 volumes_directory.lock().await.add_mount(volume_name, &volume);
233 volumes_directory
234 .serve_volume(&volume, server_end, options.as_blob)
235 .expect("serve_volume failed");
236
237 let encrypted = if options.encrypted { Some(crypt.clone()) } else { None };
238 Self {
239 state: Some(State {
240 filesystem,
241 volume,
242 volume_out_dir,
243 root,
244 volumes_directory,
245 mem_pressure_proxy,
246 }),
247 encrypted,
248 }
249 }
250
251 pub async fn close(mut self) -> DeviceHolder {
259 let State { filesystem, volume, volume_out_dir, root, volumes_directory, .. } =
260 std::mem::take(&mut self.state).unwrap();
261 volume_out_dir
262 .close()
263 .await
264 .expect("FIDL call failed")
265 .map_err(Status::from_raw)
266 .expect("close out_dir failed");
267 root.close()
270 .await
271 .expect("FIDL call failed")
272 .map_err(Status::from_raw)
273 .expect("close root failed");
274
275 volumes_directory.terminate().await;
279 drop(volumes_directory);
280
281 let store_id = volume.volume().store().store_object_id();
282
283 if volume.into_volume().try_unwrap().is_none() {
284 log::error!("References to volume still exist; hanging");
285 let () = std::future::pending().await;
286 }
287
288 filesystem.close().await.expect("close filesystem failed");
292 let device = ensure_unique_or_poison(filesystem.take_device().await);
293 device.reopen(false);
294 let filesystem = FxFilesystem::open(device).await.expect("open failed");
295 let options = FsckOptions {
296 fail_on_warning: true,
297 on_error: Box::new(|err: &FsckIssue| {
298 eprintln!("Fsck error: {:?}", err);
299 }),
300 ..Default::default()
301 };
302 fsck_with_options(filesystem.clone(), &options).await.expect("fsck failed");
303 let encrypted = if let Some(crypt) = &self.encrypted {
304 Some(crypt.clone() as Arc<dyn Crypt>)
305 } else {
306 None
307 };
308 fsck_volume_with_options(filesystem.as_ref(), &options, store_id, encrypted)
309 .await
310 .expect("fsck_volume failed");
311
312 filesystem.close().await.expect("close filesystem failed");
313 let device = ensure_unique_or_poison(filesystem.take_device().await);
314 device.reopen(false);
315
316 device
317 }
318
319 pub fn root(&self) -> &fio::DirectoryProxy {
320 &self.state.as_ref().unwrap().root
321 }
322
323 pub fn crypt(&self) -> Option<Arc<CryptBase>> {
324 self.encrypted.clone()
325 }
326
327 pub fn fs(&self) -> &Arc<FxFilesystem> {
328 &self.state.as_ref().unwrap().filesystem
329 }
330
331 pub fn volume(&self) -> &FxVolumeAndRoot {
332 &self.state.as_ref().unwrap().volume
333 }
334
335 pub fn volumes_directory(&self) -> &Arc<VolumesDirectory> {
336 &self.state.as_ref().unwrap().volumes_directory
337 }
338
339 pub fn volume_out_dir(&self) -> &fio::DirectoryProxy {
340 &self.state.as_ref().unwrap().volume_out_dir
341 }
342
343 pub fn memory_pressure_proxy(&self) -> &WatcherProxy {
344 &self.state.as_ref().unwrap().mem_pressure_proxy
345 }
346}
347
348pub struct TestCallback(Mutex<Option<Arc<dyn Fn() + Send + Sync>>>);
349pub struct TestCallbackGuard(&'static TestCallback);
350
351impl Drop for TestCallbackGuard {
352 fn drop(&mut self) {
353 *self.0.0.lock() = None;
354 }
355}
356
357impl TestCallback {
358 pub const fn new() -> Self {
359 Self(Mutex::new(None))
360 }
361
362 pub fn set<F>(&'static self, callback: F) -> TestCallbackGuard
364 where
365 F: Fn() + Send + Sync + 'static,
366 {
367 let arc: Arc<dyn Fn() + Send + Sync> = Arc::new(callback);
368 {
369 let mut inner = self.0.lock();
370 assert!(inner.is_none(), "Resetting TestCallback without dropping old guard.");
371 *inner = Some(arc.clone());
372 }
373 TestCallbackGuard(&self)
374 }
375
376 pub fn call(&self) {
377 let cb = self.0.lock().as_ref().map(|cb| cb.clone());
378 if let Some(cb) = cb {
382 cb();
383 }
384 }
385}
386
387impl Drop for TestFixture {
388 fn drop(&mut self) {
389 assert!(self.state.is_none(), "Did you forget to call TestFixture::close?");
390 }
391}
392
393pub async fn close_file_checked(file: fio::FileProxy) {
394 file.sync().await.expect("FIDL call failed").map_err(Status::from_raw).expect("sync failed");
395 file.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
396}
397
398pub async fn close_dir_checked(dir: fio::DirectoryProxy) {
399 dir.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
400}
401
402pub async fn open_file(
404 dir: &fio::DirectoryProxy,
405 path: &str,
406 flags: fio::Flags,
407 options: &fio::Options,
408) -> Result<fio::FileProxy, Error> {
409 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
410 dir.open(path, flags | fio::Flags::PROTOCOL_FILE, options, server_end.into_channel())?;
411 let _: Vec<_> = proxy.query().await?;
412 Ok(proxy)
413}
414
415pub async fn open_file_checked(
417 dir: &fio::DirectoryProxy,
418 path: &str,
419 flags: fio::Flags,
420 options: &fio::Options,
421) -> fio::FileProxy {
422 open_file(dir, path, flags, options).await.expect("open_file failed")
423}
424
425pub async fn open_dir(
427 dir: &fio::DirectoryProxy,
428 path: &str,
429 flags: fio::Flags,
430 options: &fio::Options,
431) -> Result<fio::DirectoryProxy, Error> {
432 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
433 dir.open(path, flags | fio::Flags::PROTOCOL_DIRECTORY, options, server_end.into_channel())?;
434 let _: Vec<_> = proxy.query().await?;
435 Ok(proxy)
436}
437
438pub async fn open_dir_checked(
440 dir: &fio::DirectoryProxy,
441 path: &str,
442 flags: fio::Flags,
443 options: fio::Options,
444) -> fio::DirectoryProxy {
445 open_dir(dir, path, flags, &options).await.expect("open_dir failed")
446}
447
448pub async fn write_at(file: &FxFile, offset: u64, content: &[u8]) -> Result<usize, Error> {
450 let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, file.vmo(), 0)
451 .context("stream create failed")?;
452 let content = content.to_vec();
453 unblock(move || {
454 stream
455 .write_at(zx::StreamWriteOptions::empty(), offset, &content)
456 .context("stream write failed")
457 })
458 .await
459}