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