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 volume_name.to_owned(),
144 blob_resupplied_count.clone(),
145 *volumes_directory.memory_pressure_config(),
146 )
147 .await
148 .unwrap()
149 } else {
150 FxVolumeAndRoot::new::<FxDirectory>(
151 Arc::downgrade(&volumes_directory),
152 store,
153 store_object_id,
154 volume_name.to_owned(),
155 blob_resupplied_count.clone(),
156 *volumes_directory.memory_pressure_config(),
157 )
158 .await
159 .unwrap()
160 };
161 (filesystem, vol, volumes_directory)
162 } else {
163 let filesystem = FxFilesystemBuilder::new().open(device).await.unwrap();
164 let root_volume = root_volume(filesystem.clone()).await.unwrap();
165 let store = root_volume
166 .volume(
167 volume_name,
168 StoreOptions {
169 crypt: if options.encrypted { Some(crypt.clone()) } else { None },
170 ..StoreOptions::default()
171 },
172 )
173 .await
174 .unwrap();
175 let store_object_id = store.store_object_id();
176 let volumes_directory = VolumesDirectory::new(
177 root_volume,
178 Weak::new(),
179 Some(mem_pressure),
180 blob_resupplied_count.clone(),
181 MemoryPressureConfig::default(),
182 )
183 .await
184 .unwrap();
185 let vol = if options.as_blob {
186 FxVolumeAndRoot::new::<BlobDirectory>(
187 Arc::downgrade(&volumes_directory),
188 store,
189 store_object_id,
190 volume_name.to_owned(),
191 blob_resupplied_count.clone(),
192 *volumes_directory.memory_pressure_config(),
193 )
194 .await
195 .unwrap()
196 } else {
197 FxVolumeAndRoot::new::<FxDirectory>(
198 Arc::downgrade(&volumes_directory),
199 store,
200 store_object_id,
201 volume_name.to_owned(),
202 blob_resupplied_count.clone(),
203 *volumes_directory.memory_pressure_config(),
204 )
205 .await
206 .unwrap()
207 };
208
209 (filesystem, vol, volumes_directory)
210 };
211
212 let (root, server_end) = create_proxy::<fio::DirectoryMarker>();
213 volume.root().clone().serve(fio::PERM_READABLE | fio::PERM_WRITABLE, server_end);
214
215 let (volume_out_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
216 volumes_directory.lock().await.add_mount(volume_name, &volume);
217 volumes_directory
218 .serve_volume(&volume, server_end, options.as_blob)
219 .expect("serve_volume failed");
220
221 let encrypted = if options.encrypted { Some(crypt.clone()) } else { None };
222 Self {
223 state: Some(State {
224 filesystem,
225 volume,
226 volume_out_dir,
227 root,
228 volumes_directory,
229 mem_pressure_proxy,
230 }),
231 encrypted,
232 }
233 }
234
235 pub async fn close(mut self) -> DeviceHolder {
243 let State { filesystem, volume, volume_out_dir, root, volumes_directory, .. } =
244 std::mem::take(&mut self.state).unwrap();
245 volume_out_dir
246 .close()
247 .await
248 .expect("FIDL call failed")
249 .map_err(Status::from_raw)
250 .expect("close out_dir failed");
251 root.close()
254 .await
255 .expect("FIDL call failed")
256 .map_err(Status::from_raw)
257 .expect("close root failed");
258
259 volumes_directory.terminate().await;
263 drop(volumes_directory);
264
265 let store_id = volume.volume().store().store_object_id();
266
267 if volume.into_volume().try_unwrap().is_none() {
268 log::error!("References to volume still exist; hanging");
269 let () = std::future::pending().await;
270 }
271
272 filesystem.close().await.expect("close filesystem failed");
276 let device = ensure_unique_or_poison(filesystem.take_device().await);
277 device.reopen(false);
278 let filesystem = FxFilesystem::open(device).await.expect("open failed");
279 let options = FsckOptions {
280 fail_on_warning: true,
281 on_error: Box::new(|err: &FsckIssue| {
282 eprintln!("Fsck error: {:?}", err);
283 }),
284 ..Default::default()
285 };
286 fsck_with_options(filesystem.clone(), &options).await.expect("fsck failed");
287 let encrypted = if let Some(crypt) = &self.encrypted {
288 Some(crypt.clone() as Arc<dyn Crypt>)
289 } else {
290 None
291 };
292 fsck_volume_with_options(filesystem.as_ref(), &options, store_id, encrypted)
293 .await
294 .expect("fsck_volume failed");
295
296 filesystem.close().await.expect("close filesystem failed");
297 let device = ensure_unique_or_poison(filesystem.take_device().await);
298 device.reopen(false);
299
300 device
301 }
302
303 pub fn root(&self) -> &fio::DirectoryProxy {
304 &self.state.as_ref().unwrap().root
305 }
306
307 pub fn crypt(&self) -> Option<Arc<CryptBase>> {
308 self.encrypted.clone()
309 }
310
311 pub fn fs(&self) -> &Arc<FxFilesystem> {
312 &self.state.as_ref().unwrap().filesystem
313 }
314
315 pub fn volume(&self) -> &FxVolumeAndRoot {
316 &self.state.as_ref().unwrap().volume
317 }
318
319 pub fn volumes_directory(&self) -> &Arc<VolumesDirectory> {
320 &self.state.as_ref().unwrap().volumes_directory
321 }
322
323 pub fn volume_out_dir(&self) -> &fio::DirectoryProxy {
324 &self.state.as_ref().unwrap().volume_out_dir
325 }
326
327 pub fn memory_pressure_proxy(&self) -> &WatcherProxy {
328 &self.state.as_ref().unwrap().mem_pressure_proxy
329 }
330}
331
332impl Drop for TestFixture {
333 fn drop(&mut self) {
334 assert!(self.state.is_none(), "Did you forget to call TestFixture::close?");
335 }
336}
337
338pub async fn close_file_checked(file: fio::FileProxy) {
339 file.sync().await.expect("FIDL call failed").map_err(Status::from_raw).expect("sync failed");
340 file.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
341}
342
343pub async fn close_dir_checked(dir: fio::DirectoryProxy) {
344 dir.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
345}
346
347pub async fn open_file(
349 dir: &fio::DirectoryProxy,
350 path: &str,
351 flags: fio::Flags,
352 options: &fio::Options,
353) -> Result<fio::FileProxy, Error> {
354 let (proxy, server_end) = create_proxy::<fio::FileMarker>();
355 dir.open(path, flags | fio::Flags::PROTOCOL_FILE, options, server_end.into_channel())?;
356 let _: Vec<_> = proxy.query().await?;
357 Ok(proxy)
358}
359
360pub async fn open_file_checked(
362 dir: &fio::DirectoryProxy,
363 path: &str,
364 flags: fio::Flags,
365 options: &fio::Options,
366) -> fio::FileProxy {
367 open_file(dir, path, flags, options).await.expect("open_file failed")
368}
369
370pub async fn open_dir(
372 dir: &fio::DirectoryProxy,
373 path: &str,
374 flags: fio::Flags,
375 options: &fio::Options,
376) -> Result<fio::DirectoryProxy, Error> {
377 let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
378 dir.open(path, flags | fio::Flags::PROTOCOL_DIRECTORY, options, server_end.into_channel())?;
379 let _: Vec<_> = proxy.query().await?;
380 Ok(proxy)
381}
382
383pub async fn open_dir_checked(
385 dir: &fio::DirectoryProxy,
386 path: &str,
387 flags: fio::Flags,
388 options: fio::Options,
389) -> fio::DirectoryProxy {
390 open_dir(dir, path, flags, &options).await.expect("open_dir failed")
391}
392
393pub async fn write_at(file: &FxFile, offset: u64, content: &[u8]) -> Result<usize, Error> {
395 let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, file.vmo(), 0)
396 .context("stream create failed")?;
397 let content = content.to_vec();
398 unblock(move || {
399 stream
400 .write_at(zx::StreamWriteOptions::empty(), offset, &content)
401 .context("stream write failed")
402 })
403 .await
404}