Skip to main content

fxfs_platform_testing/fuchsia/
testing.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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::PageFaultCounter;
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    pub use_driver_blob_paging: bool,
55}
56
57impl Default for TestFixtureOptions {
58    fn default() -> Self {
59        Self {
60            encrypted: true,
61            as_blob: false,
62            format: true,
63            hooks: None,
64            allow_type3_blobs: false,
65            use_driver_blob_paging: false,
66        }
67    }
68}
69
70fn ensure_unique_or_poison(holder: DeviceHolder) -> DeviceHolder {
71    if Arc::strong_count(&*holder) > 1 {
72        // All old references should be dropped by now, but they aren't. So we're going to try
73        // to crash that thread to get a stack of who is holding on to it. This is risky, and
74        // might still just crash in this thread, but it's worth a try.
75        if (*holder).poison().is_err() {
76            // Can't poison it unless it is a FakeDevice.
77            panic!("Remaining reference to device that doesn't support poison.");
78        };
79
80        // Dropping all the local references. May crash due to the poison if the extra reference was
81        // cleaned up since the last check.
82        std::mem::drop(holder);
83
84        // We've successfully poisoned the device for Drop. Now we wait and hope that the dangling
85        // reference isn't in a thread that is totally hung.
86        std::thread::sleep(std::time::Duration::from_secs(5));
87        panic!("Timed out waiting for poison to trigger.");
88    }
89    holder
90}
91
92impl TestFixture {
93    pub async fn new() -> Self {
94        Self::open(DeviceHolder::new(FakeDevice::new(16384, 512)), TestFixtureOptions::default())
95            .await
96    }
97
98    pub async fn new_with_device(device: DeviceHolder) -> Self {
99        Self::open(device, TestFixtureOptions { format: false, ..Default::default() }).await
100    }
101
102    pub async fn new_unencrypted() -> Self {
103        Self::open(
104            DeviceHolder::new(FakeDevice::new(16384, 512)),
105            TestFixtureOptions { encrypted: false, ..Default::default() },
106        )
107        .await
108    }
109
110    pub async fn open(device: DeviceHolder, options: TestFixtureOptions) -> Self {
111        let crypt: Arc<CryptBase> = Arc::new(new_insecure_crypt());
112        let (mem_pressure_proxy, watcher_server) = create_proxy();
113        let mem_pressure = MemoryPressureMonitor::try_from(watcher_server)
114            .expect("Failed to create MemoryPressureMonitor");
115
116        let page_fault_counter =
117            Arc::new(PageFaultCounter::new().expect("Failed to create PageFaultCounter"));
118        let volume_name = if options.as_blob { "blob" } else { "vol" };
119        let (filesystem, volume, volumes_directory) = if options.format {
120            let mut builder = FxFilesystemBuilder::new()
121                .format(true)
122                .allow_type3_blobs(options.allow_type3_blobs);
123            if let Some(hooks) = options.hooks {
124                builder = builder.hooks(hooks);
125            }
126            let filesystem = builder.open(device).await.unwrap();
127            let root_volume = root_volume(filesystem.clone()).await.unwrap();
128            let store = root_volume
129                .new_volume(
130                    volume_name,
131                    NewChildStoreOptions {
132                        options: StoreOptions {
133                            crypt: if options.encrypted { Some(crypt.clone()) } else { None },
134                            ..StoreOptions::default()
135                        },
136                        ..NewChildStoreOptions::default()
137                    },
138                )
139                .await
140                .unwrap();
141            let store_object_id = store.store_object_id();
142
143            let volumes_directory = VolumesDirectory::new(
144                root_volume,
145                Weak::new(),
146                Some(mem_pressure),
147                page_fault_counter.clone(),
148                MemoryPressureConfig::default(),
149            )
150            .await
151            .unwrap();
152            let vol = if options.as_blob {
153                FxVolumeAndRoot::new::<BlobDirectory>(
154                    Arc::downgrade(&volumes_directory),
155                    store,
156                    store_object_id,
157                    volume_name.to_owned(),
158                    page_fault_counter.clone(),
159                    *volumes_directory.memory_pressure_config(),
160                )
161                .await
162                .unwrap()
163            } else {
164                FxVolumeAndRoot::new::<FxDirectory>(
165                    Arc::downgrade(&volumes_directory),
166                    store,
167                    store_object_id,
168                    volume_name.to_owned(),
169                    page_fault_counter.clone(),
170                    *volumes_directory.memory_pressure_config(),
171                )
172                .await
173                .unwrap()
174            };
175            (filesystem, vol, volumes_directory)
176        } else {
177            let filesystem = FxFilesystemBuilder::new()
178                .allow_type3_blobs(options.allow_type3_blobs)
179                .open(device)
180                .await
181                .unwrap();
182            let root_volume = root_volume(filesystem.clone()).await.unwrap();
183            let store = root_volume
184                .volume(
185                    volume_name,
186                    StoreOptions {
187                        crypt: if options.encrypted { Some(crypt.clone()) } else { None },
188                        ..StoreOptions::default()
189                    },
190                )
191                .await
192                .unwrap();
193            let store_object_id = store.store_object_id();
194            let volumes_directory = VolumesDirectory::new(
195                root_volume,
196                Weak::new(),
197                Some(mem_pressure),
198                page_fault_counter.clone(),
199                MemoryPressureConfig::default(),
200            )
201            .await
202            .unwrap();
203            let vol = if options.as_blob {
204                FxVolumeAndRoot::new::<BlobDirectory>(
205                    Arc::downgrade(&volumes_directory),
206                    store,
207                    store_object_id,
208                    volume_name.to_owned(),
209                    page_fault_counter.clone(),
210                    *volumes_directory.memory_pressure_config(),
211                )
212                .await
213                .unwrap()
214            } else {
215                FxVolumeAndRoot::new::<FxDirectory>(
216                    Arc::downgrade(&volumes_directory),
217                    store,
218                    store_object_id,
219                    volume_name.to_owned(),
220                    page_fault_counter.clone(),
221                    *volumes_directory.memory_pressure_config(),
222                )
223                .await
224                .unwrap()
225            };
226
227            (filesystem, vol, volumes_directory)
228        };
229
230        if options.as_blob && options.use_driver_blob_paging {
231            volume.volume().set_use_driver_blob_paging(true);
232        }
233
234        let (root, server_end) = create_proxy::<fio::DirectoryMarker>();
235        volume.root().clone().serve(fio::PERM_READABLE | fio::PERM_WRITABLE, server_end);
236
237        let (volume_out_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
238        volumes_directory.lock().await.add_mount(volume_name, &volume);
239        volumes_directory
240            .serve_volume(&volume, server_end, options.as_blob)
241            .expect("serve_volume failed");
242
243        let encrypted = if options.encrypted { Some(crypt.clone()) } else { None };
244        Self {
245            state: Some(State {
246                filesystem,
247                volume,
248                volume_out_dir,
249                root,
250                volumes_directory,
251                mem_pressure_proxy,
252            }),
253            encrypted,
254        }
255    }
256
257    /// Closes the test fixture, shutting down the filesystem. Returns the device, which can be
258    /// reused for another TestFixture.
259    ///
260    /// Ensures that:
261    ///   * The filesystem shuts down cleanly.
262    ///   * fsck passes.
263    ///   * There are no dangling references to the device or the volume.
264    pub async fn close(mut self) -> DeviceHolder {
265        let State { filesystem, volume, volume_out_dir, root, volumes_directory, .. } =
266            std::mem::take(&mut self.state).unwrap();
267        volume_out_dir
268            .close()
269            .await
270            .expect("FIDL call failed")
271            .map_err(Status::err_from_raw)
272            .expect("close out_dir failed");
273        // Close the root node and ensure that there's no remaining references to |vol|, which would
274        // indicate a reference cycle or other leak.
275        root.close()
276            .await
277            .expect("FIDL call failed")
278            .map_err(Status::err_from_raw)
279            .expect("close root failed");
280
281        // This should terminate all volumes.  This should ensure that there are no other references
282        // to the volume (which can be associated with connections that we have not yet noticed are
283        // closed).  This will then allow `FxVolume::try_unwrap()` to work below.
284        volumes_directory.terminate().await;
285        drop(volumes_directory);
286
287        let store_id = volume.volume().store().store_object_id();
288
289        if volume.into_volume().try_unwrap().is_none() {
290            log::error!("References to volume still exist; hanging");
291            let () = std::future::pending().await;
292        }
293
294        // We have to reopen the filesystem briefly to fsck it. (We could fsck before closing, but
295        // there might be pending operations that go through after fsck but before we close the
296        // filesystem, and we want to be sure that we catch all possible issues with fsck.)
297        filesystem.close().await.expect("close filesystem failed");
298        let device = ensure_unique_or_poison(filesystem.take_device().await);
299        device.reopen(false);
300        let filesystem = FxFilesystem::open(device).await.expect("open failed");
301        let options = FsckOptions {
302            fail_on_warning: true,
303            on_error: Box::new(|err: &FsckIssue| {
304                eprintln!("Fsck error: {:?}", err);
305            }),
306            ..Default::default()
307        };
308        fsck_with_options(filesystem.clone(), &options).await.expect("fsck failed");
309        let encrypted = if let Some(crypt) = &self.encrypted {
310            Some(crypt.clone() as Arc<dyn Crypt>)
311        } else {
312            None
313        };
314        fsck_volume_with_options(filesystem.as_ref(), &options, store_id, encrypted)
315            .await
316            .expect("fsck_volume failed");
317
318        filesystem.close().await.expect("close filesystem failed");
319        let device = ensure_unique_or_poison(filesystem.take_device().await);
320        device.reopen(false);
321
322        device
323    }
324
325    pub fn root(&self) -> &fio::DirectoryProxy {
326        &self.state.as_ref().unwrap().root
327    }
328
329    pub fn crypt(&self) -> Option<Arc<CryptBase>> {
330        self.encrypted.clone()
331    }
332
333    pub fn fs(&self) -> &Arc<FxFilesystem> {
334        &self.state.as_ref().unwrap().filesystem
335    }
336
337    pub fn volume(&self) -> &FxVolumeAndRoot {
338        &self.state.as_ref().unwrap().volume
339    }
340
341    pub fn volumes_directory(&self) -> &Arc<VolumesDirectory> {
342        &self.state.as_ref().unwrap().volumes_directory
343    }
344
345    pub fn volume_out_dir(&self) -> &fio::DirectoryProxy {
346        &self.state.as_ref().unwrap().volume_out_dir
347    }
348
349    pub fn memory_pressure_proxy(&self) -> &WatcherProxy {
350        &self.state.as_ref().unwrap().mem_pressure_proxy
351    }
352}
353
354pub struct TestCallback(Mutex<Option<Arc<dyn Fn() + Send + Sync>>>);
355pub struct TestCallbackGuard(&'static TestCallback);
356
357impl Drop for TestCallbackGuard {
358    fn drop(&mut self) {
359        *self.0.0.lock() = None;
360    }
361}
362
363impl TestCallback {
364    pub const fn new() -> Self {
365        Self(Mutex::new(None))
366    }
367
368    /// Returns a guard that invalidates this callback and releases the resources when dropped.
369    pub fn set<F>(&'static self, callback: F) -> TestCallbackGuard
370    where
371        F: Fn() + Send + Sync + 'static,
372    {
373        let arc: Arc<dyn Fn() + Send + Sync> = Arc::new(callback);
374        {
375            let mut inner = self.0.lock();
376            assert!(inner.is_none(), "Resetting TestCallback without dropping old guard.");
377            *inner = Some(arc.clone());
378        }
379        TestCallbackGuard(&self)
380    }
381
382    pub fn call(&self) {
383        let cb = self.0.lock().as_ref().map(|cb| cb.clone());
384        // Call the callback outside the lock. This isn't really a race though, since just calling
385        // the callback doesn't ensure that any action will actually be done inside it, and this
386        // delay to calling the callback is impossible to differentiate from that delay.
387        if let Some(cb) = cb {
388            cb();
389        }
390    }
391}
392
393impl Drop for TestFixture {
394    fn drop(&mut self) {
395        assert!(self.state.is_none(), "Did you forget to call TestFixture::close?");
396    }
397}
398
399pub async fn close_file_checked(file: fio::FileProxy) {
400    file.sync()
401        .await
402        .expect("FIDL call failed")
403        .map_err(Status::err_from_raw)
404        .expect("sync failed");
405    file.close()
406        .await
407        .expect("FIDL call failed")
408        .map_err(Status::err_from_raw)
409        .expect("close failed");
410}
411
412pub async fn close_dir_checked(dir: fio::DirectoryProxy) {
413    dir.close()
414        .await
415        .expect("FIDL call failed")
416        .map_err(Status::err_from_raw)
417        .expect("close failed");
418}
419
420// Utility function to open a new node connection under |dir| using open.
421pub async fn open_file(
422    dir: &fio::DirectoryProxy,
423    path: &str,
424    flags: fio::Flags,
425    options: &fio::Options,
426) -> Result<fio::FileProxy, Error> {
427    let (proxy, server_end) = create_proxy::<fio::FileMarker>();
428    dir.open(path, flags | fio::Flags::PROTOCOL_FILE, options, server_end.into_channel())?;
429    let _: Vec<_> = proxy.query().await?;
430    Ok(proxy)
431}
432
433// Like |open_file|, but asserts if the open call fails.
434pub async fn open_file_checked(
435    dir: &fio::DirectoryProxy,
436    path: &str,
437    flags: fio::Flags,
438    options: &fio::Options,
439) -> fio::FileProxy {
440    open_file(dir, path, flags, options).await.expect("open_file failed")
441}
442
443// Utility function to open a new node connection under |dir|.
444pub async fn open_dir(
445    dir: &fio::DirectoryProxy,
446    path: &str,
447    flags: fio::Flags,
448    options: &fio::Options,
449) -> Result<fio::DirectoryProxy, Error> {
450    let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
451    dir.open(path, flags | fio::Flags::PROTOCOL_DIRECTORY, options, server_end.into_channel())?;
452    let _: Vec<_> = proxy.query().await?;
453    Ok(proxy)
454}
455
456// Like |open_dir|, but asserts if the open call fails.
457pub async fn open_dir_checked(
458    dir: &fio::DirectoryProxy,
459    path: &str,
460    flags: fio::Flags,
461    options: fio::Options,
462) -> fio::DirectoryProxy {
463    open_dir(dir, path, flags, &options).await.expect("open_dir failed")
464}
465
466/// Utility function to write to an `FxFile`.
467pub async fn write_at(file: &FxFile, offset: u64, content: &[u8]) -> Result<usize, Error> {
468    let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, file.vmo(), 0)
469        .context("stream create failed")?;
470    let content = content.to_vec();
471    unblock(move || {
472        stream
473            .write_at(zx::StreamWriteOptions::empty(), offset, &content)
474            .context("stream write failed")
475    })
476    .await
477}