Skip to main content

fxfs_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 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        // All old references should be dropped by now, but they aren't. So we're going to try
61        // to crash that thread to get a stack of who is holding on to it. This is risky, and
62        // might still just crash in this thread, but it's worth a try.
63        if (*holder).poison().is_err() {
64            // Can't poison it unless it is a FakeDevice.
65            panic!("Remaining reference to device that doesn't support poison.");
66        };
67
68        // Dropping all the local references. May crash due to the poison if the extra reference was
69        // cleaned up since the last check.
70        std::mem::drop(holder);
71
72        // We've successfully poisoned the device for Drop. Now we wait and hope that the dangling
73        // reference isn't in a thread that is totally hung.
74        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                    blob_resupplied_count.clone(),
144                    *volumes_directory.memory_pressure_config(),
145                )
146                .await
147                .unwrap()
148            } else {
149                FxVolumeAndRoot::new::<FxDirectory>(
150                    Arc::downgrade(&volumes_directory),
151                    store,
152                    store_object_id,
153                    blob_resupplied_count.clone(),
154                    *volumes_directory.memory_pressure_config(),
155                )
156                .await
157                .unwrap()
158            };
159            (filesystem, vol, volumes_directory)
160        } else {
161            let filesystem = FxFilesystemBuilder::new().open(device).await.unwrap();
162            let root_volume = root_volume(filesystem.clone()).await.unwrap();
163            let store = root_volume
164                .volume(
165                    volume_name,
166                    StoreOptions {
167                        crypt: if options.encrypted { Some(crypt.clone()) } else { None },
168                        ..StoreOptions::default()
169                    },
170                )
171                .await
172                .unwrap();
173            let store_object_id = store.store_object_id();
174            let volumes_directory = VolumesDirectory::new(
175                root_volume,
176                Weak::new(),
177                Some(mem_pressure),
178                blob_resupplied_count.clone(),
179                MemoryPressureConfig::default(),
180            )
181            .await
182            .unwrap();
183            let vol = if options.as_blob {
184                FxVolumeAndRoot::new::<BlobDirectory>(
185                    Arc::downgrade(&volumes_directory),
186                    store,
187                    store_object_id,
188                    blob_resupplied_count.clone(),
189                    *volumes_directory.memory_pressure_config(),
190                )
191                .await
192                .unwrap()
193            } else {
194                FxVolumeAndRoot::new::<FxDirectory>(
195                    Arc::downgrade(&volumes_directory),
196                    store,
197                    store_object_id,
198                    blob_resupplied_count.clone(),
199                    *volumes_directory.memory_pressure_config(),
200                )
201                .await
202                .unwrap()
203            };
204
205            (filesystem, vol, volumes_directory)
206        };
207
208        let (root, server_end) = create_proxy::<fio::DirectoryMarker>();
209        volume.root().clone().serve(fio::PERM_READABLE | fio::PERM_WRITABLE, server_end);
210
211        let (volume_out_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
212        volumes_directory.lock().await.add_mount(volume_name, &volume);
213        volumes_directory
214            .serve_volume(&volume, server_end, options.as_blob)
215            .expect("serve_volume failed");
216
217        let encrypted = if options.encrypted { Some(crypt.clone()) } else { None };
218        Self {
219            state: Some(State {
220                filesystem,
221                volume,
222                volume_out_dir,
223                root,
224                volumes_directory,
225                mem_pressure_proxy,
226            }),
227            encrypted,
228        }
229    }
230
231    /// Closes the test fixture, shutting down the filesystem. Returns the device, which can be
232    /// reused for another TestFixture.
233    ///
234    /// Ensures that:
235    ///   * The filesystem shuts down cleanly.
236    ///   * fsck passes.
237    ///   * There are no dangling references to the device or the volume.
238    pub async fn close(mut self) -> DeviceHolder {
239        let State { filesystem, volume, volume_out_dir, root, volumes_directory, .. } =
240            std::mem::take(&mut self.state).unwrap();
241        volume_out_dir
242            .close()
243            .await
244            .expect("FIDL call failed")
245            .map_err(Status::from_raw)
246            .expect("close out_dir failed");
247        // Close the root node and ensure that there's no remaining references to |vol|, which would
248        // indicate a reference cycle or other leak.
249        root.close()
250            .await
251            .expect("FIDL call failed")
252            .map_err(Status::from_raw)
253            .expect("close root failed");
254
255        // This should terminate all volumes.  This should ensure that there are no other references
256        // to the volume (which can be associated with connections that we have not yet noticed are
257        // closed).  This will then allow `FxVolume::try_unwrap()` to work below.
258        volumes_directory.terminate().await;
259        drop(volumes_directory);
260
261        let store_id = volume.volume().store().store_object_id();
262
263        if volume.into_volume().try_unwrap().is_none() {
264            log::error!("References to volume still exist; hanging");
265            let () = std::future::pending().await;
266        }
267
268        // We have to reopen the filesystem briefly to fsck it. (We could fsck before closing, but
269        // there might be pending operations that go through after fsck but before we close the
270        // filesystem, and we want to be sure that we catch all possible issues with fsck.)
271        filesystem.close().await.expect("close filesystem failed");
272        let device = ensure_unique_or_poison(filesystem.take_device().await);
273        device.reopen(false);
274        let filesystem = FxFilesystem::open(device).await.expect("open failed");
275        let options = FsckOptions {
276            fail_on_warning: true,
277            on_error: Box::new(|err: &FsckIssue| {
278                eprintln!("Fsck error: {:?}", err);
279            }),
280            ..Default::default()
281        };
282        fsck_with_options(filesystem.clone(), &options).await.expect("fsck failed");
283        let encrypted = if let Some(crypt) = &self.encrypted {
284            Some(crypt.clone() as Arc<dyn Crypt>)
285        } else {
286            None
287        };
288        fsck_volume_with_options(filesystem.as_ref(), &options, store_id, encrypted)
289            .await
290            .expect("fsck_volume failed");
291
292        filesystem.close().await.expect("close filesystem failed");
293        let device = ensure_unique_or_poison(filesystem.take_device().await);
294        device.reopen(false);
295
296        device
297    }
298
299    pub fn root(&self) -> &fio::DirectoryProxy {
300        &self.state.as_ref().unwrap().root
301    }
302
303    pub fn crypt(&self) -> Option<Arc<CryptBase>> {
304        self.encrypted.clone()
305    }
306
307    pub fn fs(&self) -> &Arc<FxFilesystem> {
308        &self.state.as_ref().unwrap().filesystem
309    }
310
311    pub fn volume(&self) -> &FxVolumeAndRoot {
312        &self.state.as_ref().unwrap().volume
313    }
314
315    pub fn volumes_directory(&self) -> &Arc<VolumesDirectory> {
316        &self.state.as_ref().unwrap().volumes_directory
317    }
318
319    pub fn volume_out_dir(&self) -> &fio::DirectoryProxy {
320        &self.state.as_ref().unwrap().volume_out_dir
321    }
322
323    pub fn memory_pressure_proxy(&self) -> &WatcherProxy {
324        &self.state.as_ref().unwrap().mem_pressure_proxy
325    }
326}
327
328impl Drop for TestFixture {
329    fn drop(&mut self) {
330        assert!(self.state.is_none(), "Did you forget to call TestFixture::close?");
331    }
332}
333
334pub async fn close_file_checked(file: fio::FileProxy) {
335    file.sync().await.expect("FIDL call failed").map_err(Status::from_raw).expect("sync failed");
336    file.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
337}
338
339pub async fn close_dir_checked(dir: fio::DirectoryProxy) {
340    dir.close().await.expect("FIDL call failed").map_err(Status::from_raw).expect("close failed");
341}
342
343// Utility function to open a new node connection under |dir| using open.
344pub async fn open_file(
345    dir: &fio::DirectoryProxy,
346    path: &str,
347    flags: fio::Flags,
348    options: &fio::Options,
349) -> Result<fio::FileProxy, Error> {
350    let (proxy, server_end) = create_proxy::<fio::FileMarker>();
351    dir.open(path, flags | fio::Flags::PROTOCOL_FILE, options, server_end.into_channel())?;
352    let _: Vec<_> = proxy.query().await?;
353    Ok(proxy)
354}
355
356// Like |open_file|, but asserts if the open call fails.
357pub async fn open_file_checked(
358    dir: &fio::DirectoryProxy,
359    path: &str,
360    flags: fio::Flags,
361    options: &fio::Options,
362) -> fio::FileProxy {
363    open_file(dir, path, flags, options).await.expect("open_file failed")
364}
365
366// Utility function to open a new node connection under |dir|.
367pub async fn open_dir(
368    dir: &fio::DirectoryProxy,
369    path: &str,
370    flags: fio::Flags,
371    options: &fio::Options,
372) -> Result<fio::DirectoryProxy, Error> {
373    let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
374    dir.open(path, flags | fio::Flags::PROTOCOL_DIRECTORY, options, server_end.into_channel())?;
375    let _: Vec<_> = proxy.query().await?;
376    Ok(proxy)
377}
378
379// Like |open_dir|, but asserts if the open call fails.
380pub async fn open_dir_checked(
381    dir: &fio::DirectoryProxy,
382    path: &str,
383    flags: fio::Flags,
384    options: fio::Options,
385) -> fio::DirectoryProxy {
386    open_dir(dir, path, flags, &options).await.expect("open_dir failed")
387}
388
389/// Utility function to write to an `FxFile`.
390pub async fn write_at(file: &FxFile, offset: u64, content: &[u8]) -> Result<usize, Error> {
391    let stream = zx::Stream::create(zx::StreamOptions::MODE_WRITE, file.vmo(), 0)
392        .context("stream create failed")?;
393    let content = content.to_vec();
394    unblock(move || {
395        stream
396            .write_at(zx::StreamWriteOptions::empty(), offset, &content)
397            .context("stream write failed")
398    })
399    .await
400}