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