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 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        // All old references should be dropped by now, but they aren't. So we're going to try
68        // to crash that thread to get a stack of who is holding on to it. This is risky, and
69        // might still just crash in this thread, but it's worth a try.
70        if (*holder).poison().is_err() {
71            // Can't poison it unless it is a FakeDevice.
72            panic!("Remaining reference to device that doesn't support poison.");
73        };
74
75        // Dropping all the local references. May crash due to the poison if the extra reference was
76        // cleaned up since the last check.
77        std::mem::drop(holder);
78
79        // We've successfully poisoned the device for Drop. Now we wait and hope that the dangling
80        // reference isn't in a thread that is totally hung.
81        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    /// Closes the test fixture, shutting down the filesystem. Returns the device, which can be
249    /// reused for another TestFixture.
250    ///
251    /// Ensures that:
252    ///   * The filesystem shuts down cleanly.
253    ///   * fsck passes.
254    ///   * There are no dangling references to the device or the volume.
255    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        // Close the root node and ensure that there's no remaining references to |vol|, which would
265        // indicate a reference cycle or other leak.
266        root.close()
267            .await
268            .expect("FIDL call failed")
269            .map_err(Status::from_raw)
270            .expect("close root failed");
271
272        // This should terminate all volumes.  This should ensure that there are no other references
273        // to the volume (which can be associated with connections that we have not yet noticed are
274        // closed).  This will then allow `FxVolume::try_unwrap()` to work below.
275        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        // We have to reopen the filesystem briefly to fsck it. (We could fsck before closing, but
286        // there might be pending operations that go through after fsck but before we close the
287        // filesystem, and we want to be sure that we catch all possible issues with fsck.)
288        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
360// Utility function to open a new node connection under |dir| using open.
361pub 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
373// Like |open_file|, but asserts if the open call fails.
374pub 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
383// Utility function to open a new node connection under |dir|.
384pub 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
396// Like |open_dir|, but asserts if the open call fails.
397pub 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
406/// Utility function to write to an `FxFile`.
407pub 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}