Skip to main content

fxfs/object_store/
volume.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::errors::FxfsError;
6use crate::filesystem::FxFilesystem;
7use crate::object_store::directory::Directory;
8use crate::object_store::transaction::{LockKeys, Mutation, Options, Transaction, lock_keys};
9use crate::object_store::tree_cache::TreeCache;
10use crate::object_store::{
11    ChildValue, DirType, INVALID_OBJECT_ID, LockKey, NewChildStoreOptions, ObjectDescriptor,
12    ObjectKey, ObjectStore, ObjectValue, StoreOptions, load_store_info,
13};
14use anyhow::{Context, Error, anyhow, bail, ensure};
15use std::sync::Arc;
16
17// Volumes are a grouping of an object store and a root directory within this object store. They
18// model a hierarchical tree of objects within a single store.
19//
20// Typically there will be one root volume which is referenced directly by the superblock. This root
21// volume stores references to all other volumes on the system (as volumes/foo, volumes/bar, ...).
22// For now, this hierarchy is only one deep.
23
24pub const VOLUMES_DIRECTORY: &str = "volumes";
25
26/// RootVolume is the top-level volume which stores references to all of the other Volumes.
27pub struct RootVolume {
28    _root_directory: Directory<ObjectStore>,
29    filesystem: Arc<FxFilesystem>,
30}
31
32impl RootVolume {
33    pub fn volume_directory(&self) -> &Directory<ObjectStore> {
34        self.filesystem.object_manager().volume_directory()
35    }
36
37    /// Creates a new volume under a transaction lock.
38    pub async fn new_volume(
39        &self,
40        volume_name: &str,
41        options: NewChildStoreOptions,
42    ) -> Result<Arc<ObjectStore>, Error> {
43        let root_store = self.filesystem.root_store();
44        let store;
45        let mut transaction = root_store
46            .new_transaction(
47                lock_keys![LockKey::object(
48                    root_store.store_object_id(),
49                    self.volume_directory().object_id(),
50                )],
51                Options::default(),
52            )
53            .await?;
54
55        ensure!(
56            matches!(self.volume_directory().lookup(volume_name).await?, None),
57            FxfsError::AlreadyExists
58        );
59        store = root_store
60            .new_child_store(&mut transaction, options, Box::new(TreeCache::new()))
61            .await?;
62        store.set_trace(self.filesystem.trace());
63
64        // We must register the store here because create will add mutations for the store.
65        self.filesystem.object_manager().add_store(store.clone());
66
67        // If the transaction fails, we must unregister the store.
68        struct CleanUp<'a>(&'a ObjectStore);
69        impl Drop for CleanUp<'_> {
70            fn drop(&mut self) {
71                self.0.filesystem().object_manager().forget_store(self.0.store_object_id());
72            }
73        }
74        let clean_up = CleanUp(&store);
75
76        // Actually create the store in the transaction.
77        store.create(&mut transaction).await?;
78
79        self.volume_directory()
80            .add_child_volume(&mut transaction, volume_name, store.store_object_id())
81            .await?;
82        transaction.commit().await?;
83
84        std::mem::forget(clean_up);
85
86        Ok(store)
87    }
88
89    /// Returns the volume with the given name.  This is not thread-safe.
90    pub async fn volume(
91        &self,
92        volume_name: &str,
93        options: StoreOptions,
94    ) -> Result<Arc<ObjectStore>, Error> {
95        // Lookup the volume object in the volume directory.
96        let (store_object_id, descriptor, _) = self
97            .volume_directory()
98            .lookup(volume_name)
99            .await
100            .context("Volume lookup failed")?
101            .ok_or(FxfsError::NotFound)
102            .context("Volume missing in volume directory")?;
103        match descriptor {
104            ObjectDescriptor::Volume => (),
105            _ => bail!(anyhow!(FxfsError::Inconsistent).context("Expected volume")),
106        }
107        // Lookup the object store corresponding to the volume.
108        let store = self
109            .filesystem
110            .object_manager()
111            .store(store_object_id)
112            .ok_or(FxfsError::NotFound)
113            .context("Missing volume store")?;
114        store.set_trace(self.filesystem.trace());
115        // Unlock the volume if required.
116        if let Some(crypt) = options.crypt {
117            let read_only = self.filesystem.options().read_only;
118            store.unlock_inner(crypt, read_only).await.context("Failed to unlock volume")?;
119        } else if store.is_locked() {
120            bail!(FxfsError::AccessDenied);
121        }
122        Ok(store)
123    }
124
125    /// Deletes the given volume.  Consumes `transaction` and runs `callback` during commit. The
126    /// caller must have the correct locks for the volumes directory.
127    pub async fn delete_volume(
128        &self,
129        volume_name: &str,
130        mut transaction: Transaction<'_>,
131        callback: impl FnOnce() + Send,
132    ) -> Result<(), Error> {
133        let objects_to_delete = self.delete_volume_impl(volume_name, &mut transaction).await?;
134        transaction.commit_with_callback(|_| callback()).await.context("commit")?;
135        // Tombstone the deleted objects.
136        let root_store = self.filesystem.root_store();
137        for object_id in &objects_to_delete {
138            root_store.tombstone_object(*object_id, Options::default()).await?;
139        }
140        Ok(())
141    }
142
143    async fn delete_volume_impl(
144        &self,
145        volume_name: &str,
146        transaction: &mut Transaction<'_>,
147    ) -> Result<Vec<u64>, Error> {
148        let object_id =
149            match self.volume_directory().lookup(volume_name).await?.ok_or(FxfsError::NotFound)? {
150                (object_id, ObjectDescriptor::Volume, _) => object_id,
151                _ => bail!(anyhow!(FxfsError::Inconsistent).context("Expected volume")),
152            };
153        let root_store = self.filesystem.root_store();
154
155        // Delete all the layers and encrypted mutations stored in root_store for this volume.
156        // This includes the StoreInfo itself.
157        let mut objects_to_delete = load_store_info(&root_store, object_id).await?.parent_objects();
158        objects_to_delete.push(object_id);
159
160        for object_id in &objects_to_delete {
161            root_store.adjust_refs(transaction, *object_id, -1).await?;
162        }
163        // Mark all volume data as deleted.
164        self.filesystem.allocator().mark_for_deletion(transaction, object_id);
165        // Remove the volume entry from the VolumeDirectory.
166        self.volume_directory().delete_child_volume(transaction, volume_name, object_id)?;
167        Ok(objects_to_delete)
168    }
169
170    /// Adds the required mutations to atomically replace a volume, returning a list of object IDs
171    /// of objects which can be deleted. If `dst` does not exist, this is equivalent to renaming the
172    /// volume from `src` to `dst`. The caller must have the correct locks on the volumes directory.
173    pub(crate) async fn replace_volume(
174        &self,
175        transaction: &mut Transaction<'_>,
176        src: &str,
177        dst: &str,
178    ) -> Result<Option<Vec<u64>>, Error> {
179        let src_object_id = match self.volume_directory().lookup(src).await? {
180            Some((object_id, ObjectDescriptor::Volume, _)) => Ok(object_id),
181            Some(_) => Err(FxfsError::Inconsistent),
182            None => Err(FxfsError::NotFound),
183        }?;
184
185        let replaced_objects = if let Some((_, ObjectDescriptor::Volume, _)) =
186            self.volume_directory().lookup(dst).await?
187        {
188            Some(self.delete_volume_impl(dst, transaction).await?)
189        } else {
190            None
191        };
192
193        transaction.add(
194            self.volume_directory().store().store_object_id(),
195            Mutation::replace_or_insert_object(
196                ObjectKey::child(self.volume_directory().object_id(), src, DirType::Normal),
197                ObjectValue::None,
198            ),
199        );
200
201        transaction.add(
202            self.volume_directory().store().store_object_id(),
203            Mutation::replace_or_insert_object(
204                ObjectKey::child(self.volume_directory().object_id(), dst, DirType::Normal),
205                ObjectValue::Child(ChildValue {
206                    object_id: src_object_id,
207                    object_descriptor: ObjectDescriptor::Volume,
208                }),
209            ),
210        );
211
212        Ok(replaced_objects)
213    }
214
215    /// Attempts to install the image `image_file` in the volume `src` as the volume `dst`. The
216    /// image file should be an fxfs partition image containing a volume matching the name `dst`.
217    /// The contents of the `dst` volume in the image will be installed in-place into this
218    /// filesystem, replacing an existing `dst` volume if one exists.
219    ///
220    /// There can be no other objects in `src` with extent records, and neither `src` nor `dst` can
221    /// be encrypted.
222    pub async fn install_volume(
223        &self,
224        src: &str,
225        image_file: &str,
226        dst: &str,
227    ) -> Result<(), Error> {
228        ObjectStore::install_volume(self, src, image_file, dst).await
229    }
230
231    /// Acquires a transaction with appropriate locks to remove volume |name|.
232    /// Also returns the object ID of the store which will be deleted.
233    pub async fn acquire_transaction_for_remove_volume(
234        &self,
235        name: &str,
236        extra_keys: impl IntoIterator<Item = LockKey>,
237        allow_not_found: bool,
238    ) -> Result<(u64, Transaction<'_>), Error> {
239        // Since we don't know the store object ID until we've looked it up in the volumes
240        // directory, we need to loop until we have acquired a lock on a store whose ID is the same
241        // as it was in the last iteration.
242        let volume_dir = self.volume_directory();
243        let store = volume_dir.store();
244        let extra_keys = extra_keys.into_iter();
245        let mut lock_keys = Vec::with_capacity(extra_keys.size_hint().1.unwrap_or(2) + 2);
246        lock_keys.extend(extra_keys);
247        lock_keys.push(LockKey::object(store.store_object_id(), volume_dir.object_id()));
248        let orig_len = lock_keys.len();
249        let mut transaction = None;
250        loop {
251            lock_keys.truncate(orig_len);
252            let object_id = match volume_dir.lookup(name).await? {
253                Some((object_id, ObjectDescriptor::Volume, _)) => {
254                    // We have to ensure that the store isn't flushed while we delete it, because
255                    // deleting the store will remove references to it from ObjectManager which are
256                    // then updated by flushing.
257                    lock_keys.push(LockKey::flush(object_id));
258                    object_id
259                }
260                None => {
261                    if allow_not_found {
262                        INVALID_OBJECT_ID
263                    } else {
264                        bail!(FxfsError::NotFound);
265                    }
266                }
267                _ => bail!(anyhow!(FxfsError::Inconsistent).context("Expected volume")),
268            };
269
270            // If the IDs match, return the transaction now.
271            match transaction {
272                Some(result @ (id, _)) if id == object_id => return Ok(result),
273                _ => {}
274            }
275
276            transaction = Some((
277                object_id,
278                store
279                    .new_transaction(
280                        LockKeys::Vec(lock_keys.clone()),
281                        Options { borrow_metadata_space: true, ..Default::default() },
282                    )
283                    .await?,
284            ));
285        }
286    }
287}
288
289/// Returns the root volume for the filesystem.
290pub async fn root_volume(filesystem: Arc<FxFilesystem>) -> Result<RootVolume, Error> {
291    let root_store = filesystem.root_store();
292    let root_directory = Directory::open(&root_store, root_store.root_directory_object_id())
293        .await
294        .context("Unable to open root volume directory")?;
295    Ok(RootVolume { _root_directory: root_directory, filesystem })
296}
297
298/// Returns the object IDs for all volumes.
299pub async fn list_volumes(volume_directory: &Directory<ObjectStore>) -> Result<Vec<u64>, Error> {
300    let layer_set = volume_directory.store().tree().layer_set();
301    let mut merger = layer_set.merger();
302    let mut iter = volume_directory.iter(&mut merger).await?;
303    let mut object_ids = vec![];
304    while let Some((_, id, _)) = iter.get() {
305        object_ids.push(id);
306        iter.advance().await?;
307    }
308    Ok(object_ids)
309}
310
311#[cfg(test)]
312mod tests {
313    use super::root_volume;
314    use crate::filesystem::{FxFilesystem, JournalingObject, SyncOptions};
315    use crate::fsck::{FsckOptions, fsck_volume_with_options, fsck_with_options};
316    use crate::object_handle::{ObjectHandle, WriteObjectHandle};
317    use crate::object_store::directory::Directory;
318    use crate::object_store::transaction::{Options, lock_keys};
319    use crate::object_store::{LockKey, NewChildStoreOptions, StoreOptions};
320    use fxfs_crypto::Crypt;
321    use fxfs_insecure_crypto::new_insecure_crypt;
322    use std::sync::Arc;
323    use storage_device::DeviceHolder;
324    use storage_device::fake_device::FakeDevice;
325
326    async fn do_fsck(
327        fs: &Arc<FxFilesystem>,
328        volume_name: Option<&str>,
329        crypt: Option<Arc<dyn Crypt>>,
330    ) {
331        let fsck_options = FsckOptions {
332            fail_on_warning: true,
333            on_error: Box::new(|err| eprintln!("fsck error: {:?}", err)),
334            ..Default::default()
335        };
336        fsck_with_options(fs.clone(), &fsck_options).await.expect("fsck filesystem");
337        if let Some(volume_name) = volume_name {
338            let root = root_volume(fs.clone()).await.unwrap();
339            let vol = root
340                .volume(
341                    volume_name,
342                    StoreOptions { crypt: crypt.clone(), ..StoreOptions::default() },
343                )
344                .await
345                .expect("could not open volume");
346            fsck_volume_with_options(&fs, &fsck_options, vol.store_object_id(), crypt)
347                .await
348                .expect("fsck volume");
349        }
350    }
351
352    #[fuchsia::test]
353    async fn test_lookup_nonexistent_volume() {
354        let device = DeviceHolder::new(FakeDevice::new(8192, 512));
355        let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
356        let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
357        root_volume
358            .volume(
359                "vol",
360                StoreOptions {
361                    crypt: Some(Arc::new(new_insecure_crypt())),
362                    ..StoreOptions::default()
363                },
364            )
365            .await
366            .err()
367            .expect("Volume shouldn't exist");
368        filesystem.close().await.expect("Close failed");
369    }
370
371    #[fuchsia::test]
372    async fn test_add_volume() {
373        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
374        let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
375        let crypt = Arc::new(new_insecure_crypt());
376        {
377            let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
378            let store = root_volume
379                .new_volume(
380                    "vol",
381                    NewChildStoreOptions {
382                        options: StoreOptions {
383                            crypt: Some(crypt.clone()),
384                            ..StoreOptions::default()
385                        },
386                        ..Default::default()
387                    },
388                )
389                .await
390                .expect("new_volume failed");
391            let mut transaction = filesystem
392                .root_store()
393                .new_transaction(
394                    lock_keys![LockKey::object(
395                        store.store_object_id(),
396                        store.root_directory_object_id()
397                    )],
398                    Options::default(),
399                )
400                .await
401                .expect("new transaction failed");
402            let root_directory = Directory::open(&store, store.root_directory_object_id())
403                .await
404                .expect("open failed");
405            root_directory
406                .create_child_file(&mut transaction, "foo")
407                .await
408                .expect("create_child_file failed");
409            transaction.commit().await.expect("commit failed");
410            filesystem.sync(SyncOptions::default()).await.expect("sync failed");
411        };
412        {
413            filesystem.close().await.expect("Close failed");
414            let device = filesystem.take_device().await;
415            device.reopen(false);
416            let filesystem = FxFilesystem::open(device).await.expect("open failed");
417            do_fsck(&filesystem, Some("vol"), Some(crypt)).await;
418            let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
419            // NOTE: The volume should have been unlocked by `do_fsck` so we omit `crypt` here.
420            let volume = root_volume
421                .volume("vol", StoreOptions { crypt: None, ..StoreOptions::default() })
422                .await
423                .expect("volume failed");
424            let root_directory = Directory::open(&volume, volume.root_directory_object_id())
425                .await
426                .expect("open failed");
427            root_directory.lookup("foo").await.expect("lookup failed").expect("not found");
428            filesystem.close().await.expect("Close failed");
429        };
430    }
431
432    #[fuchsia::test]
433    async fn test_delete_volume() {
434        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
435        let filesystem = FxFilesystem::new_empty(device).await.expect("new_empty failed");
436        let crypt = Arc::new(new_insecure_crypt());
437        let store_object_id;
438        let parent_objects;
439        // Add volume and a file (some data).
440        let store_id = {
441            let root_volume = root_volume(filesystem.clone()).await.expect("root_volume failed");
442            let store = root_volume
443                .new_volume(
444                    "vol",
445                    NewChildStoreOptions {
446                        options: StoreOptions {
447                            crypt: Some(crypt.clone()),
448                            ..StoreOptions::default()
449                        },
450                        ..Default::default()
451                    },
452                )
453                .await
454                .expect("new_volume failed");
455            store_object_id = store.store_object_id();
456            let mut transaction = filesystem
457                .root_store()
458                .new_transaction(
459                    lock_keys![LockKey::object(store_object_id, store.root_directory_object_id())],
460                    Options::default(),
461                )
462                .await
463                .expect("new transaction failed");
464            let root_directory = Directory::open(&store, store.root_directory_object_id())
465                .await
466                .expect("open failed");
467            let handle = root_directory
468                .create_child_file(&mut transaction, "foo")
469                .await
470                .expect("create_child_file failed");
471            transaction.commit().await.expect("commit failed");
472
473            let mut buf = handle.allocate_buffer(8192).await;
474            buf.as_mut_slice().fill(0xaa);
475            handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
476            store.flush().await.expect("flush failed");
477            filesystem.sync(SyncOptions::default()).await.expect("sync failed");
478            parent_objects = store.parent_objects();
479            // Confirm parent objects exist.
480            for object_id in &parent_objects {
481                let _ = filesystem
482                    .root_store()
483                    .get_file_size(*object_id)
484                    .await
485                    .expect("Layer file missing? Bug in test.");
486            }
487            store.store_object_id()
488        };
489        filesystem.close().await.expect("Close failed");
490        let device = filesystem.take_device().await;
491        device.reopen(false);
492        let filesystem = FxFilesystem::open(device).await.expect("open failed");
493        do_fsck(&filesystem, Some("vol"), Some(crypt.clone())).await;
494        {
495            // Expect 8kiB accounted to the new volume.
496            assert_eq!(
497                filesystem.allocator().get_owner_allocated_bytes().get(&store_object_id),
498                Some(&8192)
499            );
500            let root = root_volume(filesystem.clone()).await.expect("root_volume failed");
501            let transaction = filesystem
502                .root_store()
503                .new_transaction(
504                    lock_keys![
505                        LockKey::object(
506                            root.volume_directory().store().store_object_id(),
507                            root.volume_directory().object_id(),
508                        ),
509                        LockKey::flush(store_id)
510                    ],
511                    Options { borrow_metadata_space: true, ..Default::default() },
512                )
513                .await
514                .expect("new_transaction failed");
515            root.delete_volume("vol", transaction, || {}).await.expect("delete_volume");
516            // Confirm data allocation is gone.
517            assert_eq!(
518                filesystem
519                    .allocator()
520                    .get_owner_allocated_bytes()
521                    .get(&store_object_id)
522                    .unwrap_or(&0),
523                &0,
524            );
525            // Confirm volume entry is gone.
526            root.volume(
527                "vol",
528                StoreOptions { crypt: Some(crypt.clone()), ..StoreOptions::default() },
529            )
530            .await
531            .err()
532            .expect("volume shouldn't exist anymore.");
533        }
534        filesystem.close().await.expect("Close failed");
535        let device = filesystem.take_device().await;
536        device.reopen(false);
537        // All artifacts of the original volume should be gone.
538        let filesystem = FxFilesystem::open(device).await.expect("open failed");
539        do_fsck(&filesystem, None, None).await;
540        for object_id in &parent_objects {
541            let _ = filesystem
542                .root_store()
543                .get_file_size(*object_id)
544                .await
545                .err()
546                .expect("File wasn't deleted.");
547        }
548        filesystem.close().await.expect("Close failed");
549    }
550
551    #[fuchsia::test]
552    async fn test_replace_volume() {
553        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
554        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
555        // Add volume "vol" with a file "foo".
556        {
557            let root = root_volume(fs.clone()).await.expect("root_volume failed");
558            let store = root.new_volume("vol", NewChildStoreOptions::default()).await.unwrap();
559            let mut transaction = fs
560                .root_store()
561                .new_transaction(
562                    lock_keys![LockKey::object(
563                        store.store_object_id(),
564                        store.root_directory_object_id()
565                    )],
566                    Options::default(),
567                )
568                .await
569                .unwrap();
570            let root_directory =
571                Directory::open(&store, store.root_directory_object_id()).await.unwrap();
572            let _ = root_directory.create_child_file(&mut transaction, "foo").await.unwrap();
573            transaction.commit().await.expect("commit failed");
574        }
575        // Add a second volume "vol2" with a file "foo2".
576        {
577            let root = root_volume(fs.clone()).await.expect("root_volume failed");
578            let store = root
579                .new_volume("vol2", NewChildStoreOptions::default())
580                .await
581                .expect("new_volume failed");
582            let mut transaction = fs
583                .root_store()
584                .new_transaction(
585                    lock_keys![LockKey::object(
586                        store.store_object_id(),
587                        store.root_directory_object_id()
588                    )],
589                    Options::default(),
590                )
591                .await
592                .expect("new transaction failed");
593            let root_directory = Directory::open(&store, store.root_directory_object_id())
594                .await
595                .expect("open failed");
596            let _ = root_directory
597                .create_child_file(&mut transaction, "foo2")
598                .await
599                .expect("create_child_file failed");
600            transaction.commit().await.expect("commit failed");
601        }
602        // Replace "vol" with "vol2", and ensure the filesystem and installed volume passes fsck.
603        {
604            let root = root_volume(fs.clone()).await.expect("root_volume failed");
605            let mut transaction =
606                root.acquire_transaction_for_remove_volume("vol", [], false).await.unwrap().1;
607            root.replace_volume(&mut transaction, "vol2", "vol").await.unwrap();
608            transaction.commit().await.unwrap();
609            do_fsck(&fs, Some("vol"), None).await;
610        }
611        fs.close().await.expect("Close failed");
612        let device = fs.take_device().await;
613        device.reopen(false);
614        let fs = FxFilesystem::open(device).await.unwrap();
615        do_fsck(&fs, Some("vol"), None).await;
616        {
617            let root = root_volume(fs.clone()).await.unwrap();
618            // vol2 should now have replaced vol
619            root.volume("vol2", StoreOptions::default())
620                .await
621                .err()
622                .expect("vol2 shouldn't exist anymore.");
623            let vol = root.volume("vol", StoreOptions::default()).await.unwrap();
624            let dir = Directory::open(&vol, vol.root_directory_object_id()).await.unwrap();
625            // The contents of "foo" should have been replaced entirely with those from "foo2".
626            assert!(dir.lookup("foo").await.unwrap().is_none(), "foo should not be present");
627            assert!(dir.lookup("foo2").await.unwrap().is_some(), "foo2 should be present");
628        }
629        fs.close().await.unwrap();
630    }
631
632    #[fuchsia::test]
633    async fn test_create_volume_with_guid() {
634        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
635        let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
636        let guid = [1u8; 16];
637        {
638            let root = root_volume(fs.clone()).await.expect("root_volume failed");
639            let store = root
640                .new_volume("vol", NewChildStoreOptions { guid: Some(guid), ..Default::default() })
641                .await
642                .unwrap();
643            assert_eq!(store.store_info().unwrap().guid, guid);
644        }
645        fs.close().await.expect("Close failed");
646        let device = fs.take_device().await;
647        device.reopen(false);
648        let fs = FxFilesystem::open(device).await.unwrap();
649        {
650            let root = root_volume(fs.clone()).await.unwrap();
651            let vol = root.volume("vol", StoreOptions::default()).await.unwrap();
652            assert_eq!(vol.guid(), guid);
653        }
654        fs.close().await.unwrap();
655    }
656}