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