| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| crypt::{insecure::InsecureCrypt, Crypt}, |
| filesystem::{Filesystem, FxFilesystem, JournalingObject, OpenFxFilesystem, OpenOptions}, |
| fsck::{ |
| errors::{FsckError, FsckFatal, FsckIssue, FsckWarning}, |
| fsck_with_options, FsckOptions, |
| }, |
| lsm_tree::{ |
| simple_persistent_layer::SimplePersistentLayerWriter, |
| types::{Item, ItemRef, Key, LayerIterator, LayerWriter, Value}, |
| }, |
| object_handle::{ObjectHandle, Writer, INVALID_OBJECT_ID}, |
| object_store::{ |
| allocator::{AllocatorKey, AllocatorValue, CoalescingIterator, SimpleAllocator}, |
| directory::Directory, |
| transaction::{self, Options, TransactionHandler}, |
| volume::root_volume, |
| AttributeKey, ExtentValue, HandleOptions, Mutation, ObjectAttributes, ObjectDescriptor, |
| ObjectKey, ObjectKind, ObjectStore, ObjectValue, Timestamp, |
| }, |
| round::round_down, |
| serialized_types::VersionedLatest, |
| }, |
| anyhow::{Context, Error}, |
| assert_matches::assert_matches, |
| fuchsia_async as fasync, |
| std::{ |
| ops::{Bound, Deref}, |
| sync::{Arc, Mutex}, |
| }, |
| storage_device::{fake_device::FakeDevice, DeviceHolder}, |
| }; |
| |
| const TEST_DEVICE_BLOCK_SIZE: u32 = 512; |
| const TEST_DEVICE_BLOCK_COUNT: u64 = 8192; |
| |
| struct FsckTest { |
| filesystem: Option<OpenFxFilesystem>, |
| errors: Mutex<Vec<FsckIssue>>, |
| crypt: Option<Arc<dyn Crypt>>, |
| } |
| |
| impl FsckTest { |
| async fn new() -> Self { |
| let filesystem = FxFilesystem::new_empty(DeviceHolder::new(FakeDevice::new( |
| TEST_DEVICE_BLOCK_COUNT, |
| TEST_DEVICE_BLOCK_SIZE, |
| ))) |
| .await |
| .expect("new_empty failed"); |
| |
| Self { filesystem: Some(filesystem), errors: Mutex::new(vec![]), crypt: None } |
| } |
| async fn remount(&mut self) -> Result<(), Error> { |
| let fs = self.filesystem.take().unwrap(); |
| fs.close().await.expect("Failed to close FS"); |
| let device = fs.take_device().await; |
| device.reopen(); |
| self.filesystem = Some( |
| FxFilesystem::open_with_options( |
| device, |
| OpenOptions { read_only: true, ..Default::default() }, |
| ) |
| .await |
| .context("Failed to open FS")?, |
| ); |
| Ok(()) |
| } |
| async fn run(&self, halt_on_error: bool) -> Result<(), Error> { |
| let options = FsckOptions { |
| fail_on_warning: true, |
| halt_on_error, |
| do_slow_passes: true, |
| verbose: false, |
| on_error: |err| { |
| if err.is_error() { |
| eprintln!("Fsck error: {:?}", &err); |
| } else { |
| println!("Fsck warning: {:?}", &err); |
| } |
| self.errors.lock().unwrap().push(err.clone()); |
| }, |
| }; |
| fsck_with_options(&self.filesystem(), self.crypt.clone(), options).await |
| } |
| fn filesystem(&self) -> Arc<FxFilesystem> { |
| self.filesystem.as_ref().unwrap().deref().clone() |
| } |
| fn errors(&self) -> Vec<FsckIssue> { |
| self.errors.lock().unwrap().clone() |
| } |
| fn get_crypt(&mut self) -> Arc<dyn Crypt> { |
| self.crypt.get_or_insert_with(|| Arc::new(InsecureCrypt::new())).clone() |
| } |
| } |
| |
| // Creates a new layer file containing |items| and writes them in order into |store|, skipping all |
| // normal validation. This allows bad records to be inserted into the object store (although they |
| // will still be subject to merging). |
| // Doing this in the root store might cause a variety of unrelated failures. |
| async fn install_items_in_store<K: Key, V: Value>( |
| filesystem: &Arc<FxFilesystem>, |
| store: &ObjectStore, |
| items: impl AsRef<[Item<K, V>]>, |
| ) { |
| let device = filesystem.device(); |
| let root_store = filesystem.root_store(); |
| let mut transaction = filesystem |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let layer_handle = ObjectStore::create_object( |
| &root_store, |
| &mut transaction, |
| HandleOptions::default(), |
| store.crypt().as_deref(), |
| ) |
| .await |
| .expect("create_object failed"); |
| transaction.commit().await.expect("commit failed"); |
| |
| { |
| let mut writer = SimplePersistentLayerWriter::<Writer<'_>, K, V>::new( |
| Writer::new(&layer_handle), |
| filesystem.block_size(), |
| ) |
| .await |
| .expect("writer new"); |
| for item in items.as_ref() { |
| writer.write(item.as_item_ref()).await.expect("write failed"); |
| } |
| writer.flush().await.expect("flush failed"); |
| } |
| |
| let mut store_info = store.store_info(); |
| store_info.layers.push(layer_handle.object_id()); |
| let mut store_info_vec = vec![]; |
| store_info.serialize_with_version(&mut store_info_vec).expect("serialize failed"); |
| let mut buf = device.allocate_buffer(store_info_vec.len()); |
| buf.as_mut_slice().copy_from_slice(&store_info_vec[..]); |
| |
| let store_info_handle = ObjectStore::open_object( |
| &root_store, |
| store.store_info_handle_object_id().unwrap(), |
| HandleOptions::default(), |
| None, |
| ) |
| .await |
| .expect("open store info handle failed"); |
| let mut transaction = |
| store_info_handle.new_transaction().await.expect("new_transaction failed"); |
| store_info_handle.txn_write(&mut transaction, 0, buf.as_ref()).await.expect("txn_write failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| /* TODO(fxbug.dev/92054): Fix this test |
| #[fasync::run_singlethreaded(test)] |
| async fn test_missing_graveyard() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_store = fs.root_store(); |
| let mut transaction = fs |
| .clone() |
| .new_transaction( |
| &[], |
| transaction::Options { |
| skip_journal_checks: true, |
| borrow_metadata_space: true, |
| ..Default::default() |
| }, |
| ) |
| .await |
| .expect("New transaction failed"); |
| transaction.add(root_store.store_object_id, Mutation::graveyard_directory(u64::MAX - 1)); |
| transaction.commit().await.expect("Commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [ |
| FsckIssue::Error(FsckError::ExtraAllocations(_)), |
| FsckIssue::Error(FsckError::AllocatedBytesMismatch(..)) |
| ] |
| ); |
| } |
| */ |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_extra_allocation() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| // We need a discontiguous allocation, and some blocks will have been used up by other |
| // things, so allocate the very last block. Note that changing our allocation strategy |
| // might break this test. |
| let end = |
| round_down(TEST_DEVICE_BLOCK_SIZE as u64 * TEST_DEVICE_BLOCK_COUNT, fs.block_size()); |
| fs.allocator() |
| .mark_allocated(&mut transaction, 4, end - fs.block_size()..end) |
| .await |
| .expect("mark_allocated failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [ |
| FsckIssue::Error(FsckError::ExtraAllocations(_)), |
| FsckIssue::Error(FsckError::AllocatedBytesMismatch(..)) |
| ] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_misaligned_allocation() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| // We need a discontiguous allocation, and some blocks will have been used up by other |
| // things, so allocate the very last block. Note that changing our allocation strategy |
| // might break this test. |
| let end = |
| round_down(TEST_DEVICE_BLOCK_SIZE as u64 * TEST_DEVICE_BLOCK_COUNT, fs.block_size()); |
| fs.allocator() |
| .mark_allocated(&mut transaction, 99, end - fs.block_size() + 1..end) |
| .await |
| .expect("mark_allocated failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MisalignedAllocation(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_malformed_allocation() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_store = fs.root_store(); |
| let device = fs.device(); |
| // We need to manually insert the record into the allocator's LSM tree directly, since the |
| // allocator code checks range validity. |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let layer_handle = ObjectStore::create_object( |
| &root_store, |
| &mut transaction, |
| HandleOptions::default(), |
| None, |
| ) |
| .await |
| .expect("create_object failed"); |
| transaction.commit().await.expect("commit failed"); |
| |
| { |
| let mut writer = |
| SimplePersistentLayerWriter::<Writer<'_>, AllocatorKey, AllocatorValue>::new( |
| Writer::new(&layer_handle), |
| fs.block_size(), |
| ) |
| .await |
| .expect("writer new"); |
| // We also need a discontiguous allocation, and some blocks will have been used up by |
| // other things, so allocate the very last block. Note that changing our allocation |
| // strategy might break this test. |
| let end = round_down( |
| TEST_DEVICE_BLOCK_SIZE as u64 * TEST_DEVICE_BLOCK_COUNT, |
| fs.block_size(), |
| ); |
| let item = Item::new( |
| AllocatorKey { device_range: end..end - fs.block_size() }, |
| AllocatorValue::Abs { count: 2, owner_object_id: 9 }, |
| ); |
| writer.write(item.as_item_ref()).await.expect("write failed"); |
| writer.flush().await.expect("flush failed"); |
| } |
| let mut allocator_info = fs.allocator().info(); |
| allocator_info.layers.push(layer_handle.object_id()); |
| let mut allocator_info_vec = vec![]; |
| allocator_info.serialize_with_version(&mut allocator_info_vec).expect("serialize failed"); |
| let mut buf = device.allocate_buffer(allocator_info_vec.len()); |
| buf.as_mut_slice().copy_from_slice(&allocator_info_vec[..]); |
| |
| let handle = ObjectStore::open_object( |
| &root_store, |
| fs.allocator().object_id(), |
| HandleOptions::default(), |
| None, |
| ) |
| .await |
| .expect("open allocator handle failed"); |
| let mut transaction = handle.new_transaction().await.expect("new_transaction failed"); |
| handle.txn_write(&mut transaction, 0, buf.as_ref()).await.expect("txn_write failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MalformedAllocation(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_misaligned_extent_in_root_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_store = fs.root_store(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| root_store.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(555, 0, 1..fs.block_size()), |
| ObjectValue::Extent(ExtentValue::new(1)), |
| ), |
| ); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MisalignedExtent(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_malformed_extent_in_root_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_store = fs.root_store(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| root_store.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(555, 0, fs.block_size()..0), |
| ObjectValue::Extent(ExtentValue::new(1)), |
| ), |
| ); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MalformedExtent(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_misaligned_extent_in_child_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let volume = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| volume.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(555, 0, 1..fs.block_size()), |
| ObjectValue::Extent(ExtentValue::new(1)), |
| ), |
| ); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MisalignedExtent(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_malformed_extent_in_child_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let volume = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| volume.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(555, 0, fs.block_size()..0), |
| ObjectValue::Extent(ExtentValue::new(1)), |
| ), |
| ); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(true).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MalformedExtent(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_allocation_mismatch() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let allocator = fs.allocator().as_any().downcast::<SimpleAllocator>().unwrap(); |
| let range = { |
| let layer_set = allocator.tree().layer_set(); |
| let mut merger = layer_set.merger(); |
| let iter = allocator.iter(&mut merger, Bound::Unbounded).await.expect("iter failed"); |
| let ItemRef { key: AllocatorKey { device_range }, .. } = |
| iter.get().expect("missing item"); |
| device_range.clone() |
| }; |
| // Replace owner_object_id with a different owner and bump count to something impossible. |
| allocator |
| .tree() |
| .replace_or_insert(Item::new( |
| AllocatorKey { device_range: range.clone() }, |
| AllocatorValue::Abs { count: 2, owner_object_id: 10 }, |
| )) |
| .await; |
| allocator.flush().await.expect("flush failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::AllocationMismatch(..)),]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_missing_allocation() { |
| let test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let allocator = fs.allocator().as_any().downcast::<SimpleAllocator>().unwrap(); |
| let key = { |
| let layer_set = allocator.tree().layer_set(); |
| let mut merger = layer_set.merger(); |
| let iter = allocator.iter(&mut merger, Bound::Unbounded).await.expect("iter failed"); |
| let iter = CoalescingIterator::new(iter).await.expect("filter failed"); |
| let ItemRef { key, .. } = iter.get().expect("missing item"); |
| // 'key' points at the first allocation record, which will be for the super blocks. |
| key.clone() |
| }; |
| let lower_bound = key.lower_bound_for_merge_into(); |
| allocator.tree().merge_into(Item::new(key, AllocatorValue::None), &lower_bound).await; |
| } |
| // We intentionally don't remount here, since the above tree mutation wouldn't persist |
| // otherwise. |
| // Structuring this test to actually persist a bad allocation layer file is possible but tricky |
| // since flushing or committing transactions might itself perform allocations, and it isn't that |
| // important. |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::MissingAllocation(..)),]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_too_many_object_refs() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| |
| let root_store = fs.root_store(); |
| let root_directory = Directory::open(&root_store, root_store.root_directory_object_id()) |
| .await |
| .expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let child_file = root_directory |
| .create_child_file(&mut transaction, "child_file") |
| .await |
| .expect("create_child_file failed"); |
| let child_dir = root_directory |
| .create_child_dir(&mut transaction, "child_dir") |
| .await |
| .expect("create_child_directory failed"); |
| |
| // Add an extra reference to the child file. |
| child_dir |
| .insert_child(&mut transaction, "test", child_file.object_id(), ObjectDescriptor::File) |
| .await |
| .expect("insert_child failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [ |
| FsckIssue::Error(FsckError::RefCountMismatch(..)), |
| FsckIssue::Error(FsckError::ObjectCountMismatch(..)) |
| ] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_too_few_object_refs() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_store = fs.root_store(); |
| |
| // Create an object but no directory entry referencing that object, so it will end up with a |
| // reference count of one, but zero references. |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| ObjectStore::create_object(&root_store, &mut transaction, HandleOptions::default(), None) |
| .await |
| .expect("create_object failed"); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Warning(FsckWarning::OrphanedObject(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_missing_object_tree_layer_file() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let volume = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| ObjectStore::create_object(&volume, &mut transaction, HandleOptions::default(), None) |
| .await |
| .expect("create_object failed"); |
| transaction.commit().await.expect("commit failed"); |
| volume.flush().await.expect("Flush store failed"); |
| let id = { |
| let layers = volume.tree().immutable_layer_set(); |
| assert!(!layers.layers.is_empty()); |
| layers.layers[0].handle().unwrap().object_id() |
| }; |
| fs.root_store() |
| .tombstone(id, transaction::Options::default()) |
| .await |
| .expect("tombstone failed"); |
| } |
| |
| test.remount().await.expect_err("Remount succeeded"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_missing_object_store_handle() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store_id = { |
| let volume = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| volume.store_object_id() |
| }; |
| fs.root_store() |
| .tombstone(store_id, transaction::Options::default()) |
| .await |
| .expect("tombstone failed"); |
| } |
| |
| test.remount().await.expect_err("Remount succeeded"); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_misordered_layer_file() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![ |
| Item::new(ObjectKey::extent(5, 0, 10..20), ObjectValue::deleted_extent()), |
| Item::new(ObjectKey::extent(1, 0, 0..5), ObjectValue::deleted_extent()), |
| ], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Fatal(FsckFatal::MisOrderedLayerFile(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_overlapping_keys_in_layer_file() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![ |
| Item::new(ObjectKey::extent(1, 0, 0..20), ObjectValue::deleted_extent()), |
| Item::new(ObjectKey::extent(1, 0, 10..30), ObjectValue::deleted_extent()), |
| Item::new(ObjectKey::extent(1, 0, 15..40), ObjectValue::deleted_extent()), |
| ], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Fatal(FsckFatal::OverlappingKeysInLayerFile(..))] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_unexpected_record_in_layer_file() { |
| let mut test = FsckTest::new().await; |
| // This test relies on the value below being something that doesn't deserialize to a valid ObjectValue. |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new(ObjectKey::object(0), 0xffffffffu32)], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Fatal(FsckFatal::MalformedLayerFile(..))]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_mismatched_key_and_value() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new(ObjectKey::object(10), ObjectValue::Attribute { size: 100 })], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Error(FsckError::MalformedObjectRecord(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_link_to_root_directory() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| root_directory |
| .insert_child( |
| &mut transaction, |
| "a", |
| store.root_directory_object_id(), |
| ObjectDescriptor::Directory, |
| ) |
| .await |
| .expect("insert_child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::RootObjectHasParent(..)), ..]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_multiple_links_to_directory() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| root_directory |
| .insert_child(&mut transaction, "a", 10, ObjectDescriptor::Directory) |
| .await |
| .expect("insert_child failed"); |
| root_directory |
| .insert_child(&mut transaction, "b", 10, ObjectDescriptor::Directory) |
| .await |
| .expect("insert_child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Error(FsckError::MultipleLinksToDirectory(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_conflicting_link_types() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| root_directory |
| .insert_child(&mut transaction, "a", 10, ObjectDescriptor::Directory) |
| .await |
| .expect("insert_child failed"); |
| root_directory |
| .insert_child(&mut transaction, "b", 10, ObjectDescriptor::File) |
| .await |
| .expect("insert_child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Error(FsckError::ConflictingTypeForLink(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_volume_in_child_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| root_directory |
| .insert_child(&mut transaction, "a", 10, ObjectDescriptor::Volume) |
| .await |
| .expect("Create child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::VolumeInChildStore(..)), ..]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_children_on_file() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let object_id = root_directory |
| .create_child_file(&mut transaction, "a'") |
| .await |
| .expect("Create child failed") |
| .object_id(); |
| transaction.commit().await.expect("commit transaction failed"); |
| |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new( |
| ObjectKey::child(object_id, "foo"), |
| ObjectValue::Child { object_id: 11, object_descriptor: ObjectDescriptor::File }, |
| )], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::FileHasChildren(..)), ..]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_attribute_on_directory() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new( |
| ObjectKey::attribute(store.root_directory_object_id(), 1, AttributeKey::Size), |
| ObjectValue::attribute(100), |
| )], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!(test.errors()[..], [FsckIssue::Error(FsckError::AttributeOnDirectory(..)), ..]); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_orphaned_attribute() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new( |
| ObjectKey::attribute(10, 1, AttributeKey::Size), |
| ObjectValue::attribute(100), |
| )], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Warning(FsckWarning::OrphanedAttribute(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_records_for_tombstoned_object() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![ |
| Item::new(ObjectKey::object(10), ObjectValue::None), |
| Item::new( |
| ObjectKey::attribute(10, 1, AttributeKey::Size), |
| ObjectValue::attribute(100), |
| ), |
| ], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Error(FsckError::TombstonedObjectHasRecords(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_invalid_object_in_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| install_items_in_store( |
| &fs, |
| store.as_ref(), |
| vec![Item::new(ObjectKey::object(INVALID_OBJECT_ID), ObjectValue::Some)], |
| ) |
| .await; |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Warning(FsckWarning::InvalidObjectIdInStore(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_invalid_child_in_store() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| root_directory |
| .insert_child(&mut transaction, "a", INVALID_OBJECT_ID, ObjectDescriptor::File) |
| .await |
| .expect("Insert child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [FsckIssue::Warning(FsckWarning::InvalidObjectIdInStore(..)), ..] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_link_cycle() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| let root_directory = |
| Directory::open(&store, store.root_directory_object_id()).await.expect("open failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let parent = root_directory |
| .create_child_dir(&mut transaction, "a") |
| .await |
| .expect("Create child failed"); |
| let child = |
| parent.create_child_dir(&mut transaction, "b").await.expect("Create child failed"); |
| child |
| .insert_child(&mut transaction, "c", parent.object_id(), ObjectDescriptor::Directory) |
| .await |
| .expect("Insert child failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [ |
| FsckIssue::Error(FsckError::MultipleLinksToDirectory(..)), |
| FsckIssue::Error(FsckError::SubDirCountMismatch(..)), |
| FsckIssue::Error(FsckError::ObjectCountMismatch(..)), |
| FsckIssue::Error(FsckError::LinkCycle(..)), |
| .. |
| ] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_file_length_mismatch() { |
| let mut test = FsckTest::new().await; |
| |
| { |
| let fs = test.filesystem(); |
| let device = fs.device(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let handle = |
| ObjectStore::create_object(&store, &mut transaction, HandleOptions::default(), None) |
| .await |
| .expect("create object failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| let buf = device.allocate_buffer(1); |
| handle.txn_write(&mut transaction, 1_048_576, buf.as_ref()).await.expect("write failed"); |
| transaction.commit().await.expect("commit transaction failed"); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| store.store_object_id(), |
| Mutation::replace_or_insert_object( |
| ObjectKey::attribute(handle.object_id(), handle.attribute_id(), AttributeKey::Size), |
| ObjectValue::attribute(123), |
| ), |
| ); |
| transaction.add( |
| store.store_object_id(), |
| Mutation::replace_or_insert_object( |
| ObjectKey::object(handle.object_id()), |
| ObjectValue::Object { |
| kind: ObjectKind::File { refs: 1, allocated_size: 123 }, |
| attributes: ObjectAttributes { |
| creation_time: Timestamp::now(), |
| modification_time: Timestamp::now(), |
| }, |
| }, |
| ), |
| ); |
| transaction.commit().await.expect("commit transaction failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| assert_matches!( |
| test.errors()[..], |
| [ |
| FsckIssue::Error(FsckError::ExtentExceedsLength(..)), |
| FsckIssue::Error(FsckError::AllocatedSizeMismatch(..)), |
| .. |
| ] |
| ); |
| } |
| |
| #[fasync::run_singlethreaded(test)] |
| async fn test_spurious_extents() { |
| let mut test = FsckTest::new().await; |
| const SPURIOUS_OFFSET: u64 = 100 << 20; |
| |
| { |
| let fs = test.filesystem(); |
| let root_volume = root_volume(&fs).await.unwrap(); |
| let store = root_volume.new_volume("vol", Some(test.get_crypt())).await.unwrap(); |
| |
| let mut transaction = fs |
| .clone() |
| .new_transaction(&[], Options::default()) |
| .await |
| .expect("new_transaction failed"); |
| transaction.add( |
| store.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(555, 0, 0..4096), |
| ObjectValue::Extent(ExtentValue::new(SPURIOUS_OFFSET)), |
| ), |
| ); |
| transaction.add( |
| store.store_object_id(), |
| Mutation::insert_object( |
| ObjectKey::extent(store.root_directory_object_id(), 0, 0..4096), |
| ObjectValue::Extent(ExtentValue::new(SPURIOUS_OFFSET)), |
| ), |
| ); |
| transaction.commit().await.expect("commit failed"); |
| } |
| |
| test.remount().await.expect("Remount failed"); |
| test.run(false).await.expect_err("Fsck should fail"); |
| let mut found = 0; |
| for e in test.errors() { |
| match e { |
| FsckIssue::Warning(FsckWarning::ExtentForDirectory(..)) => found |= 1, |
| FsckIssue::Warning(FsckWarning::ExtentForNonexistentObject(..)) => found |= 2, |
| _ => {} |
| } |
| } |
| assert_eq!(found, 3, "Missing expected errors: {:?}", test.errors()); |
| } |