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