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