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