1mod bootstrap_handle;
20mod checksum_list;
21mod reader;
22pub mod super_block;
23mod writer;
24
25use crate::checksum::{Checksum, Checksums};
26use crate::errors::FxfsError;
27use crate::filesystem::{
28 ApplyContext, ApplyMode, FlushReason, ForceMajor, FxFilesystem, SyncOptions,
29};
30use crate::hooks::HooksHandle;
31use crate::log::*;
32use crate::lsm_tree::types::LayerIterator;
33use crate::object_handle::{ObjectHandle as _, ReadObjectHandle};
34use crate::object_store::allocator::Allocator;
35use crate::object_store::data_object_handle::OverwriteOptions;
36use crate::object_store::extent_record::{ExtentMode, ExtentValue};
37use crate::object_store::graveyard::Graveyard;
38use crate::object_store::journal::bootstrap_handle::BootstrapObjectHandle;
39use crate::object_store::journal::checksum_list::ChecksumList;
40use crate::object_store::journal::reader::{JournalReader, ReadResult};
41use crate::object_store::journal::super_block::{
42 SuperBlockHeader, SuperBlockInstance, SuperBlockManager,
43};
44use crate::object_store::journal::writer::JournalWriter;
45use crate::object_store::object_manager::ObjectManager;
46use crate::object_store::object_record::{AttributeKey, ObjectKey, ObjectKeyData, ObjectValue};
47use crate::object_store::transaction::{
48 AllocatorMutation, LockKey, Mutation, MutationV56, MutationV57, MutationV59, MutationV61,
49 MutationV62, ObjectMutationIterator, ObjectStoreMutation, Options, ReservationOptions,
50 TRANSACTION_MAX_JOURNAL_USAGE, Transaction, lock_keys,
51};
52use crate::object_store::{
53 AssocObj, AttributeId, DataObjectHandle, Extent, HandleOptions, HandleOwner, INVALID_OBJECT_ID,
54 Item, ItemRef, NewChildStoreOptions, ObjectStore, ReservedId,
55};
56use crate::range::RangeExt;
57use crate::round::round_div;
58use crate::serialized_types::{LATEST_VERSION, Migrate, Version, Versioned, migrate_to_version};
59use anyhow::{Context, Error, anyhow, bail, ensure};
60use core::iter::Iterator;
61use event_listener::Event;
62use fprint::TypeFingerprint;
63use fuchsia_inspect::NumericProperty;
64use fuchsia_sync::Mutex;
65use futures::FutureExt as _;
66use futures::future::poll_fn;
67use rustc_hash::FxHashMap as HashMap;
68use serde::{Deserialize, Serialize};
69use smallvec::SmallVec;
70use static_assertions::const_assert;
71use std::clone::Clone;
72use std::collections::HashSet;
73use std::num::NonZero;
74use std::ops::{Bound, Range};
75use std::sync::atomic::{AtomicBool, Ordering};
76use std::sync::{Arc, OnceLock};
77use std::task::{Poll, Waker};
78use storage_device::Device;
79use storage_units::BlockSize;
80
81pub const BLOCK_SIZE: BlockSize = BlockSize::SIZE_4KIB;
83
84const CHUNK_SIZE: u64 = 131_072;
86const_assert!(CHUNK_SIZE > TRANSACTION_MAX_JOURNAL_USAGE);
87
88pub const DEFAULT_RECLAIM_SIZE: u64 = 524_288;
90
91pub const RESERVED_SPACE: u64 = 1_048_576;
94
95const RESET_XOR: u64 = 0xffffffffffffffff;
100
101pub type JournalCheckpoint = JournalCheckpointV32;
105
106#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TypeFingerprint)]
107pub struct JournalCheckpointV32 {
108 pub file_offset: u64,
109
110 pub checksum: Checksum,
113
114 pub version: Version,
118}
119
120pub type JournalRecord = JournalRecordV62;
121
122#[allow(clippy::large_enum_variant)]
123#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
124#[cfg_attr(fuzz, derive(arbitrary::Arbitrary))]
125pub enum JournalRecordV62 {
126 EndBlock,
127 Mutation {
128 object_id: u64,
129 mutation: MutationV62,
130 },
131
132 Commit,
134
135 Discard(u64),
137
138 DidFlushDevice(u64),
148
149 DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
158
159 CommitBorrowed(u64),
162}
163
164#[allow(clippy::large_enum_variant)]
165#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
166#[migrate_to_version(JournalRecordV62)]
167pub enum JournalRecordV61 {
168 EndBlock,
169 Mutation { object_id: u64, mutation: MutationV61 },
170 Commit,
171 Discard(u64),
172 DidFlushDevice(u64),
173 DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
174 CommitBorrowed(u64),
175}
176
177#[allow(clippy::large_enum_variant)]
178#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
179pub enum JournalRecordV60 {
180 EndBlock,
181 Mutation { object_id: u64, mutation: MutationV59 },
182 Commit,
183 Discard(u64),
184 DidFlushDevice(u64),
185 DataChecksums(u64, Range<u64>, crate::checksum::ChecksumsV38, bool),
186}
187
188impl From<JournalRecordV60> for JournalRecordV61 {
189 fn from(record: JournalRecordV60) -> Self {
190 match record {
191 JournalRecordV60::EndBlock => Self::EndBlock,
192 JournalRecordV60::Mutation { object_id, mutation } => {
193 Self::Mutation { object_id, mutation: mutation.into() }
194 }
195 JournalRecordV60::Commit => Self::Commit,
196 JournalRecordV60::Discard(offset) => Self::Discard(offset),
197 JournalRecordV60::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
198 JournalRecordV60::DataChecksums(owner, range, checksums, first_write) => {
199 Self::DataChecksums(owner, range, checksums, first_write)
200 }
201 }
202 }
203}
204
205#[allow(clippy::large_enum_variant)]
206#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
207pub enum JournalRecordV59 {
208 EndBlock,
209 Mutation { object_id: u64, mutation: MutationV59 },
210 Commit,
211 Discard(u64),
212 DidFlushDevice(u64),
213 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
214}
215
216impl From<JournalRecordV59> for JournalRecordV60 {
217 fn from(record: JournalRecordV59) -> Self {
218 match record {
219 JournalRecordV59::EndBlock => Self::EndBlock,
220 JournalRecordV59::Mutation { object_id, mutation } => {
221 Self::Mutation { object_id, mutation }
222 }
223 JournalRecordV59::Commit => Self::Commit,
224 JournalRecordV59::Discard(offset) => Self::Discard(offset),
225 JournalRecordV59::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
226 JournalRecordV59::DataChecksums(range, checksums, first_write) => {
227 Self::DataChecksums(INVALID_OBJECT_ID, range, checksums, first_write)
233 }
234 }
235 }
236}
237
238#[allow(clippy::large_enum_variant)]
239#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
240#[migrate_to_version(JournalRecordV59)]
241pub enum JournalRecordV57 {
242 EndBlock,
243 Mutation { object_id: u64, mutation: MutationV57 },
244 Commit,
245 Discard(u64),
246 DidFlushDevice(u64),
247 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
248}
249
250#[allow(clippy::large_enum_variant)]
251#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
252#[migrate_to_version(JournalRecordV57)]
253pub enum JournalRecordV56 {
254 EndBlock,
255 Mutation { object_id: u64, mutation: MutationV56 },
256 Commit,
257 Discard(u64),
258 DidFlushDevice(u64),
259 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
260}
261
262pub(super) fn journal_handle_options() -> HandleOptions {
263 HandleOptions { skip_journal_checks: true, ..Default::default() }
264}
265
266pub struct Journal {
272 objects: Arc<ObjectManager>,
273 hooks: Arc<HooksHandle>,
274 handle: OnceLock<DataObjectHandle<ObjectStore>>,
275 super_block_manager: SuperBlockManager,
276 inner: Mutex<Inner>,
277 writer_mutex: Mutex<()>,
278 sync_mutex: futures::lock::Mutex<()>,
279 trace: AtomicBool,
280
281 reclaim_event: Event,
283}
284
285#[derive(Clone, Copy, Debug, PartialEq, Eq)]
286enum CompactionState {
287 Idle,
288 Running,
289 Forced,
290}
291
292impl CompactionState {
293 fn is_running(self) -> bool {
294 !matches!(self, Self::Idle)
295 }
296}
297
298struct Inner {
299 super_block_header: SuperBlockHeader,
300
301 zero_offset: Option<u64>,
303
304 device_flushed_offset: u64,
306
307 needs_did_flush_device: bool,
309
310 writer: JournalWriter,
312
313 output_reset_version: bool,
316
317 flush_waker: Option<Waker>,
319
320 terminate: bool,
322
323 terminate_reason: Option<Error>,
325
326 disable_compactions: bool,
328
329 compactions_paused: bool,
331
332 compaction_state: CompactionState,
334
335 sync_waker: Option<Waker>,
337
338 flushed_offset: u64,
340
341 valid_to: u64,
346
347 discard_offset: Option<u64>,
351
352 reclaim_size: u64,
356
357 image_builder_mode: Option<SuperBlockInstance>,
358
359 barriers_enabled: bool,
363
364 needs_barrier: bool,
367}
368
369impl Inner {
370 fn terminate(&mut self, reason: Option<Error>) {
371 self.terminate = true;
372
373 if let Some(err) = reason {
374 error!(error:? = err; "Terminating journal");
375 if let Some(prev_err) = self.terminate_reason.as_ref() {
377 error!(error:? = prev_err; "Journal previously terminated");
378 } else {
379 self.terminate_reason = Some(err);
380 }
381 }
382
383 if let Some(waker) = self.flush_waker.take() {
384 waker.wake();
385 }
386 if let Some(waker) = self.sync_waker.take() {
387 waker.wake();
388 }
389 }
390}
391
392pub struct JournalOptions {
393 pub reclaim_size: u64,
397
398 pub barriers_enabled: bool,
402}
403
404impl Default for JournalOptions {
405 fn default() -> Self {
406 JournalOptions { reclaim_size: DEFAULT_RECLAIM_SIZE, barriers_enabled: false }
407 }
408}
409
410struct JournaledTransactions {
411 transactions: Vec<JournaledTransaction>,
412 device_flushed_offset: u64,
413}
414
415#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
417pub enum BorrowedMetadata {
418 #[default]
420 None,
421
422 Borrowed(u64),
425
426 LegacyBorrowed(u64),
428}
429
430#[derive(Debug, Default)]
431pub struct JournaledTransaction {
432 pub checkpoint: JournalCheckpoint,
433 pub root_parent_mutations: Vec<Mutation>,
434 pub root_mutations: Vec<Mutation>,
435 pub non_root_mutations: Vec<(u64, Mutation)>,
437 pub end_offset: u64,
438 pub checksums: Vec<JournaledChecksums>,
439
440 pub end_flush: Option<(u64, u64)>,
443
444 pub volume_deleted: Option<u64>,
446
447 pub borrowed_metadata: BorrowedMetadata,
449
450 pub object_journal_usage: SmallVec<[(u64, u64); 2]>,
452}
453
454impl JournaledTransaction {
455 fn new(checkpoint: JournalCheckpoint) -> Self {
456 Self { checkpoint, ..Default::default() }
457 }
458}
459
460const VOLUME_DELETED: u64 = u64::MAX;
461
462#[derive(Debug)]
463pub struct JournaledChecksums {
464 pub owner_object_id: u64,
465 pub device_range: Range<u64>,
466 pub checksums: Checksums,
467 pub first_write: bool,
468}
469
470pub trait JournalHandle: ReadObjectHandle {
473 fn end_offset(&self) -> Option<u64>;
477 fn push_extent(&mut self, added_offset: u64, device_range: Range<u64>);
481 fn discard_extents(&mut self, discard_offset: u64);
483}
484
485impl<S: HandleOwner> JournalHandle for DataObjectHandle<S> {
489 fn end_offset(&self) -> Option<u64> {
490 None
491 }
492 fn push_extent(&mut self, _added_offset: u64, _device_range: Range<u64>) {
493 }
495 fn discard_extents(&mut self, _discard_offset: u64) {
496 }
498}
499
500#[fxfs_trace::trace]
501impl Journal {
502 pub fn new(
503 objects: Arc<ObjectManager>,
504 options: JournalOptions,
505 hooks: Arc<HooksHandle>,
506 ) -> Journal {
507 let starting_checksum = rand::random_range(1..u64::MAX);
508 Journal {
509 objects: objects,
510 hooks,
511 handle: OnceLock::new(),
512 super_block_manager: SuperBlockManager::new(),
513 inner: Mutex::new(Inner {
514 super_block_header: SuperBlockHeader::default(),
515 zero_offset: None,
516 device_flushed_offset: 0,
517 needs_did_flush_device: false,
518 writer: JournalWriter::new(BLOCK_SIZE, starting_checksum),
519 output_reset_version: false,
520 flush_waker: None,
521 terminate: false,
522 terminate_reason: None,
523 disable_compactions: false,
524 compactions_paused: false,
525 compaction_state: CompactionState::Idle,
526 sync_waker: None,
527 flushed_offset: 0,
528 valid_to: 0,
529 discard_offset: None,
530 reclaim_size: options.reclaim_size,
531 image_builder_mode: None,
532 barriers_enabled: options.barriers_enabled,
533 needs_barrier: false,
534 }),
535 writer_mutex: Mutex::new(()),
536 sync_mutex: futures::lock::Mutex::new(()),
537 trace: AtomicBool::new(false),
538 reclaim_event: Event::new(),
539 }
540 }
541
542 pub fn set_trace(&self, trace: bool) {
543 let old_value = self.trace.swap(trace, Ordering::Relaxed);
544 if trace != old_value {
545 info!(trace; "J: trace");
546 }
547 }
548
549 pub fn set_image_builder_mode(&self, mode: Option<SuperBlockInstance>) {
550 self.inner.lock().image_builder_mode = mode;
551 if let Some(instance) = mode {
552 *self.super_block_manager.next_instance.lock() = instance;
553 }
554 }
555
556 pub fn image_builder_mode(&self) -> Option<SuperBlockInstance> {
557 self.inner.lock().image_builder_mode
558 }
559
560 #[cfg(feature = "migration")]
561 pub fn set_filesystem_uuid(&self, uuid: &[u8; 16]) -> Result<(), Error> {
562 ensure!(
563 self.inner.lock().image_builder_mode.is_some(),
564 "Can only set filesystem uuid in image builder mode."
565 );
566 self.inner.lock().super_block_header.guid.0 = uuid::Uuid::from_bytes(*uuid);
567 Ok(())
568 }
569
570 pub(crate) async fn read_superblocks(
571 &self,
572 device: Arc<dyn Device>,
573 block_size: BlockSize,
574 ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
575 self.super_block_manager.load(device, block_size).await
576 }
577
578 fn validate_mutation(
582 &self,
583 mutation: &Mutation,
584 block_size: BlockSize,
585 device_size: u64,
586 ) -> bool {
587 match mutation {
588 Mutation::ObjectStore(ObjectStoreMutation {
589 item:
590 Item {
591 key:
592 ObjectKey {
593 data: ObjectKeyData::Attribute(_, AttributeKey::Extent(extent)),
594 ..
595 },
596 value: ObjectValue::Extent(ExtentValue::Some { device_offset, mode, .. }),
597 ..
598 },
599 ..
600 }) => {
601 if extent.is_empty() || !block_size.is_aligned(extent) {
602 return false;
603 }
604 let len = extent.length().unwrap();
605 if let ExtentMode::Cow(checksums) = mode {
606 if checksums.len() > 0 {
607 if len % checksums.len() as u64 != 0 {
608 return false;
609 }
610 if !block_size.is_aligned(len / checksums.len() as u64) {
611 return false;
612 }
613 }
614 }
615 if !block_size.is_aligned(device_offset)
616 || *device_offset >= device_size
617 || device_size - *device_offset < len
618 {
619 return false;
620 }
621 }
622 Mutation::ObjectStore(_) => {}
623 Mutation::EncryptedObjectStore(_) => {}
624 Mutation::Allocator(AllocatorMutation::Allocate { device_range, owner_object_id }) => {
625 return !device_range.is_empty()
626 && *owner_object_id != INVALID_OBJECT_ID
627 && device_range.end <= device_size;
628 }
629 Mutation::Allocator(AllocatorMutation::Deallocate {
630 device_range,
631 owner_object_id,
632 }) => {
633 return !device_range.is_empty()
634 && *owner_object_id != INVALID_OBJECT_ID
635 && device_range.end <= device_size;
636 }
637 Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
638 return *owner_object_id != INVALID_OBJECT_ID;
639 }
640 Mutation::Allocator(AllocatorMutation::SetLimit { owner_object_id, .. }) => {
641 return *owner_object_id != INVALID_OBJECT_ID;
642 }
643 Mutation::BeginFlush => {}
644 Mutation::EndFlush(_) => {}
645 Mutation::DeleteVolume => {}
646 Mutation::UpdateBorrowed(_) => {}
647 Mutation::UpdateMutationsKey(_) => {}
648 Mutation::CreateInternalDir(owner_object_id) => {
649 return *owner_object_id != INVALID_OBJECT_ID;
650 }
651 }
652 true
653 }
654
655 fn update_checksum_list(
657 &self,
658 journal_offset: u64,
659 mutation: &Mutation,
660 checksum_list: &mut ChecksumList,
661 ) -> Result<(), Error> {
662 match mutation {
663 Mutation::ObjectStore(_) => {}
664 Mutation::Allocator(AllocatorMutation::Deallocate { device_range, .. }) => {
665 checksum_list.mark_deallocated(journal_offset, device_range.clone().into());
666 }
667 Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
668 checksum_list.mark_for_deletion(journal_offset, *owner_object_id);
669 }
670 _ => {}
671 }
672 Ok(())
673 }
674
675 #[trace]
677 pub async fn replay(
678 &self,
679 filesystem: Arc<FxFilesystem>,
680 on_new_allocator: Option<Box<dyn Fn(Arc<Allocator>) + Send + Sync>>,
681 ) -> Result<(), Error> {
682 let block_size = filesystem.block_size();
683
684 let (super_block, root_parent) =
685 self.super_block_manager.load(filesystem.device(), block_size).await?;
686
687 let root_parent = Arc::new(ObjectStore::attach_filesystem(root_parent, filesystem.clone()));
688
689 self.objects.set_root_parent_store(root_parent.clone());
690 let allocator =
691 Arc::new(Allocator::new(filesystem.clone(), super_block.allocator_object_id));
692 if let Some(on_new_allocator) = on_new_allocator {
693 on_new_allocator(allocator.clone());
694 }
695 self.objects.set_allocator(allocator.clone());
696 self.objects.init_replay_metadata_reservation(
697 super_block.journal_checkpoint.version,
698 &super_block.reserved_metadata_space,
699 );
700 self.objects.set_last_end_offset(super_block.super_block_journal_file_offset);
701 {
702 let mut inner = self.inner.lock();
703 inner.super_block_header = super_block.clone();
704 }
705
706 let device = filesystem.device();
707
708 let mut handle;
709 {
710 let root_parent_layer = root_parent.tree().mutable_layer();
711 let mut iter = root_parent_layer.seek(Bound::Included(&ObjectKey::attribute(
712 super_block.journal_object_id,
713 AttributeId::DATA,
714 AttributeKey::Extent(Extent::search_key_from_offset(
715 BLOCK_SIZE.align_down(super_block.journal_checkpoint.file_offset),
716 )),
717 )));
718 let start_offset = if let Some(ItemRef {
719 key:
720 ObjectKey {
721 data:
722 ObjectKeyData::Attribute(AttributeId::DATA, AttributeKey::Extent(extent)),
723 ..
724 },
725 ..
726 }) = iter.get()
727 {
728 extent.start
729 } else {
730 0
731 };
732 handle = BootstrapObjectHandle::new_with_start_offset(
733 super_block.journal_object_id,
734 device.clone(),
735 start_offset,
736 );
737 while let Some(item) = iter.get() {
738 if !match item.into() {
739 Some((
740 object_id,
741 AttributeId::DATA,
742 extent,
743 ExtentValue::Some { device_offset, .. },
744 )) if object_id == super_block.journal_object_id => {
745 if let Some(end_offset) = handle.end_offset() {
746 if extent.start != end_offset {
747 bail!(anyhow!(FxfsError::Inconsistent).context(format!(
748 "Unexpected journal extent {:?}, expected start: {}",
749 item, end_offset
750 )));
751 }
752 }
753 handle.push_extent(
754 0, *device_offset
756 ..*device_offset + extent.length().context("Invalid extent")?,
757 );
758 true
759 }
760 _ => false,
761 } {
762 break;
763 }
764 iter.advance().await.context("Failed to advance root parent store iterator")?;
765 }
766 }
767
768 let mut reader = JournalReader::new(handle, &super_block.journal_checkpoint);
769 let JournaledTransactions { mut transactions, device_flushed_offset } = self
770 .read_transactions(&mut reader, None, INVALID_OBJECT_ID)
771 .await
772 .context("Reading transactions for replay")?;
773
774 let mut checksum_list = ChecksumList::new(device_flushed_offset);
776 let mut valid_to = reader.journal_file_checkpoint().file_offset;
777 let device_size = device.size();
778 'bad_replay: for JournaledTransaction {
779 checkpoint,
780 root_parent_mutations,
781 root_mutations,
782 non_root_mutations,
783 checksums,
784 ..
785 } in &transactions
786 {
787 for JournaledChecksums { owner_object_id, device_range, checksums, first_write } in
788 checksums
789 {
790 checksum_list
791 .push(
792 checkpoint.file_offset,
793 *owner_object_id,
794 device_range.clone(),
795 checksums.maybe_as_ref().context("Malformed checksums")?,
796 *first_write,
797 )
798 .context("Pushing journal checksum records to checksum list")?;
799 }
800 for mutation in root_parent_mutations
801 .iter()
802 .chain(root_mutations)
803 .chain(non_root_mutations.iter().map(|(_, m)| m))
804 {
805 if !self.validate_mutation(mutation, block_size, device_size) {
806 info!(mutation:?; "Stopping replay at bad mutation");
807 valid_to = checkpoint.file_offset;
808 break 'bad_replay;
809 }
810 self.update_checksum_list(checkpoint.file_offset, &mutation, &mut checksum_list)?;
811 }
812 }
813
814 let valid_to = checksum_list
817 .verify(device.as_ref(), valid_to)
818 .await
819 .context("Failed to validate checksums")?;
820
821 let mut last_checkpoint = reader.journal_file_checkpoint();
824 let mut journal_offsets = super_block.journal_file_offsets.clone();
825
826 for (
829 index,
830 JournaledTransaction {
831 checkpoint,
832 root_parent_mutations,
833 end_flush,
834 volume_deleted,
835 ..
836 },
837 ) in transactions.iter_mut().enumerate()
838 {
839 if checkpoint.file_offset >= valid_to {
840 last_checkpoint = checkpoint.clone();
841
842 transactions.truncate(index);
844 break;
845 }
846
847 let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
848 for mutation in root_parent_mutations.drain(..) {
849 self.objects
850 .apply_mutation(
851 super_block.root_parent_store_object_id,
852 mutation,
853 &context,
854 AssocObj::None,
855 )
856 .context("Failed to replay root parent store mutations")?;
857 }
858
859 if let Some((object_id, journal_offset)) = end_flush {
860 journal_offsets.insert(*object_id, *journal_offset);
861 }
862
863 if let Some(object_id) = volume_deleted {
864 journal_offsets.insert(*object_id, VOLUME_DELETED);
865 }
866 }
867
868 let root_store = ObjectStore::open(&root_parent, super_block.root_store_object_id, None)
870 .await
871 .context("Unable to open root store")?;
872
873 ensure!(
874 !root_store.is_encrypted(),
875 anyhow!(FxfsError::Inconsistent).context("Root store is encrypted")
876 );
877 self.objects.set_root_store(root_store);
878
879 let root_store_offset =
880 journal_offsets.get(&super_block.root_store_object_id).copied().unwrap_or(0);
881
882 for JournaledTransaction { checkpoint, root_mutations, .. } in &mut transactions {
884 if checkpoint.file_offset < root_store_offset {
885 continue;
886 }
887
888 let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
889 for mutation in root_mutations.drain(..) {
890 self.objects
891 .apply_mutation(
892 super_block.root_store_object_id,
893 mutation,
894 &context,
895 AssocObj::None,
896 )
897 .context("Failed to replay root store mutations")?;
898 }
899 }
900
901 allocator.open().await.context("Failed to open allocator")?;
903
904 for JournaledTransaction {
906 checkpoint,
907 non_root_mutations,
908 end_offset,
909 borrowed_metadata,
910 object_journal_usage,
911 ..
912 } in transactions
913 {
914 self.objects
915 .replay_mutations(
916 non_root_mutations,
917 &object_journal_usage,
918 &journal_offsets,
919 &ApplyContext { mode: ApplyMode::Replay, checkpoint },
920 end_offset,
921 borrowed_metadata,
922 )
923 .await
924 .context("Failed to replay mutations")?;
925 }
926
927 allocator.on_replay_complete().await.context("Failed to complete replay for allocator")?;
928
929 let discarded_to =
930 if last_checkpoint.file_offset != reader.journal_file_checkpoint().file_offset {
931 Some(reader.journal_file_checkpoint().file_offset)
932 } else {
933 None
934 };
935
936 {
938 if last_checkpoint.file_offset < super_block.super_block_journal_file_offset {
939 return Err(anyhow!(FxfsError::Inconsistent).context(format!(
940 "journal replay cut short; journal finishes at {}, but super-block was \
941 written at {}",
942 last_checkpoint.file_offset, super_block.super_block_journal_file_offset
943 )));
944 }
945 let handle = ObjectStore::open_object(
946 &root_parent,
947 super_block.journal_object_id,
948 journal_handle_options(),
949 None,
950 )
951 .await
952 .with_context(|| {
953 format!(
954 "Failed to open journal file (object id: {})",
955 super_block.journal_object_id
956 )
957 })?;
958 let _ = self.handle.set(handle);
959 let mut inner = self.inner.lock();
960 reader.skip_to_end_of_block();
961 let mut writer_checkpoint = reader.journal_file_checkpoint();
962
963 std::mem::drop(reader);
965
966 writer_checkpoint.checksum ^= RESET_XOR;
968 writer_checkpoint.version = LATEST_VERSION;
969 inner.flushed_offset = writer_checkpoint.file_offset;
970
971 inner.device_flushed_offset = inner.flushed_offset;
973
974 inner.writer.seek(writer_checkpoint);
975 inner.output_reset_version = true;
976 inner.valid_to = last_checkpoint.file_offset;
977 if last_checkpoint.file_offset < inner.flushed_offset {
978 inner.discard_offset = Some(last_checkpoint.file_offset);
979 }
980 }
981
982 self.objects
983 .on_replay_complete()
984 .await
985 .context("Failed to complete replay for object manager")?;
986
987 let required = self.objects.required_reservation();
988 let borrowed = self.objects.borrowed_metadata_space();
989 info!(
990 checkpoint = last_checkpoint.file_offset,
991 discarded_to,
992 metadata_reservation:% =
993 format_args!("{}({} - {})", required - borrowed, required, borrowed),
994 store_borrowed:? = self.objects.stores_borrowed();
995 "replay complete"
996 );
997 Ok(())
998 }
999
1000 async fn read_transactions(
1001 &self,
1002 reader: &mut JournalReader,
1003 end_offset: Option<u64>,
1004 object_id_filter: u64,
1005 ) -> Result<JournaledTransactions, Error> {
1006 let mut transactions = Vec::new();
1007 let (mut device_flushed_offset, root_parent_store_object_id, root_store_object_id) = {
1008 let super_block = &self.inner.lock().super_block_header;
1009 (
1010 super_block.super_block_journal_file_offset,
1011 super_block.root_parent_store_object_id,
1012 super_block.root_store_object_id,
1013 )
1014 };
1015 let mut current_transaction = None;
1016 let mut begin_flush_offsets = HashMap::default();
1017 let mut stores_deleted = HashSet::new();
1018 loop {
1019 let checkpoint = reader.journal_file_checkpoint();
1021 if let Some(end_offset) = end_offset {
1022 if checkpoint.file_offset >= end_offset {
1023 break;
1024 }
1025 }
1026 let result = reader
1027 .deserialize::<JournalRecord>()
1028 .await
1029 .context("Failed to deserialize journal record")?;
1030 match result {
1031 ReadResult::Reset(_) => {
1032 if current_transaction.is_some() {
1033 current_transaction = None;
1034 transactions.pop();
1035 }
1036 let offset = reader.journal_file_checkpoint().file_offset;
1037 if offset > device_flushed_offset {
1038 device_flushed_offset = offset;
1039 }
1040 }
1041 ReadResult::Some(record) => match record {
1042 JournalRecord::EndBlock => {
1043 reader.skip_to_end_of_block();
1044 }
1045 JournalRecord::Mutation { object_id, mutation } => {
1046 let record_start_offset = checkpoint.file_offset;
1047 let current_transaction = match current_transaction.as_mut() {
1048 None => {
1049 transactions.push(JournaledTransaction::new(checkpoint));
1050 current_transaction = transactions.last_mut();
1051 current_transaction.as_mut().unwrap()
1052 }
1053 Some(transaction) => transaction,
1054 };
1055
1056 if stores_deleted.contains(&object_id) {
1057 bail!(
1058 anyhow!(FxfsError::Inconsistent)
1059 .context("Encountered mutations for deleted store")
1060 );
1061 }
1062
1063 match &mutation {
1064 Mutation::BeginFlush => {
1065 begin_flush_offsets
1066 .insert(object_id, current_transaction.checkpoint.file_offset);
1067 }
1068 Mutation::EndFlush(_) => {
1069 if let Some(offset) = begin_flush_offsets.remove(&object_id) {
1070 if let Some(deleted_volume) =
1071 ¤t_transaction.volume_deleted
1072 {
1073 if *deleted_volume == object_id {
1074 bail!(anyhow!(FxfsError::Inconsistent).context(
1075 "Multiple EndFlush/DeleteVolume mutations in a \
1076 single transaction for the same object"
1077 ));
1078 }
1079 }
1080 if current_transaction
1088 .end_flush
1089 .replace((object_id, offset + 1))
1090 .is_some()
1091 {
1092 bail!(anyhow!(FxfsError::Inconsistent).context(
1093 "Multiple EndFlush mutations in a \
1094 single transaction"
1095 ));
1096 }
1097 }
1098 }
1099 Mutation::DeleteVolume => {
1100 if let Some((flushed_object, _)) = ¤t_transaction.end_flush {
1101 if *flushed_object == object_id {
1102 bail!(anyhow!(FxfsError::Inconsistent).context(
1103 "Multiple EndFlush/DeleteVolume mutations in a \
1104 single transaction for the same object"
1105 ));
1106 }
1107 }
1108 if current_transaction.volume_deleted.replace(object_id).is_some() {
1109 bail!(anyhow!(FxfsError::Inconsistent).context(
1110 "Multiple DeleteVolume mutations in a single \
1111 transaction"
1112 ));
1113 }
1114 stores_deleted.insert(object_id);
1115 }
1116 Mutation::UpdateBorrowed(borrowed) => {
1117 ensure!(
1118 object_id == 0,
1119 anyhow!(FxfsError::Inconsistent)
1120 .context("Invalid object_id for UpdateBorrowed")
1121 );
1122 current_transaction.borrowed_metadata =
1123 BorrowedMetadata::LegacyBorrowed(*borrowed);
1124 continue;
1125 }
1126 _ => {}
1127 }
1128
1129 let record_len =
1130 reader.journal_file_checkpoint().file_offset - record_start_offset;
1131
1132 if (object_id_filter == INVALID_OBJECT_ID || object_id_filter == object_id)
1135 && self.should_apply(object_id, ¤t_transaction.checkpoint)
1136 {
1137 if object_id == root_parent_store_object_id {
1138 current_transaction.root_parent_mutations.push(mutation);
1139 } else if object_id == root_store_object_id {
1140 current_transaction.root_mutations.push(mutation);
1141 } else {
1142 if mutation.counts_towards_object_journal_usage() {
1143 if let Some((last_id, usage)) =
1144 current_transaction.object_journal_usage.last_mut()
1145 && *last_id == object_id
1146 {
1147 *usage += record_len;
1148 } else if let Some((_, usage)) = current_transaction
1149 .object_journal_usage
1150 .iter_mut()
1151 .find(|(id, _)| *id == object_id)
1152 {
1153 *usage += record_len;
1154 } else {
1155 current_transaction
1156 .object_journal_usage
1157 .push((object_id, record_len));
1158 }
1159 }
1160 current_transaction.non_root_mutations.push((object_id, mutation));
1161 }
1162 }
1163 }
1164 JournalRecord::DataChecksums(
1165 owner_object_id,
1166 device_range,
1167 checksums,
1168 first_write,
1169 ) => {
1170 ensure!(
1171 checkpoint.version.major < 60 || owner_object_id != INVALID_OBJECT_ID,
1172 anyhow!(FxfsError::Inconsistent)
1173 .context("Invalid owner_object_id in DataChecksums")
1174 );
1175 if stores_deleted.contains(&owner_object_id) {
1176 bail!(
1177 anyhow!(FxfsError::Inconsistent)
1178 .context("Encountered DataChecksums for deleted store")
1179 );
1180 }
1181 let current_transaction = match current_transaction.as_mut() {
1182 None => {
1183 transactions.push(JournaledTransaction::new(checkpoint));
1184 current_transaction = transactions.last_mut();
1185 current_transaction.as_mut().unwrap()
1186 }
1187 Some(transaction) => transaction,
1188 };
1189 current_transaction.checksums.push(JournaledChecksums {
1190 owner_object_id,
1191 device_range,
1192 checksums,
1193 first_write,
1194 });
1195 }
1196 JournalRecord::Commit | JournalRecord::CommitBorrowed(_) => {
1197 if current_transaction.is_none()
1203 && object_id_filter == INVALID_OBJECT_ID
1204 && matches!(record, JournalRecord::CommitBorrowed(_))
1205 {
1206 transactions.push(JournaledTransaction::new(checkpoint));
1207 current_transaction = transactions.last_mut();
1208 }
1209 if let Some(&mut JournaledTransaction {
1210 ref checkpoint,
1211 ref root_parent_mutations,
1212 ref mut end_offset,
1213 ref mut borrowed_metadata,
1214 ..
1215 }) = current_transaction.take()
1216 {
1217 for mutation in root_parent_mutations {
1218 if let Mutation::ObjectStore(ObjectStoreMutation {
1222 item:
1223 Item {
1224 key:
1225 ObjectKey {
1226 object_id,
1227 data:
1228 ObjectKeyData::Attribute(
1229 AttributeId::DATA,
1230 AttributeKey::Extent(extent),
1231 ),
1232 ..
1233 },
1234 value:
1235 ObjectValue::Extent(ExtentValue::Some {
1236 device_offset,
1237 ..
1238 }),
1239 ..
1240 },
1241 ..
1242 }) = mutation
1243 {
1244 let handle = reader.handle();
1247 if *object_id != handle.object_id() {
1248 continue;
1249 }
1250 if let Some(end_offset) = handle.end_offset() {
1251 if extent.start != end_offset {
1252 bail!(anyhow!(FxfsError::Inconsistent).context(
1253 format!(
1254 "Unexpected journal extent {:?} -> {}, \
1255 expected start: {}",
1256 *extent, device_offset, end_offset,
1257 )
1258 ));
1259 }
1260 }
1261 handle.push_extent(
1262 checkpoint.file_offset,
1263 *device_offset
1264 ..*device_offset
1265 + extent.length().context("Invalid extent")?,
1266 );
1267 }
1268 }
1269 *end_offset = reader.journal_file_checkpoint().file_offset;
1270 if let JournalRecord::CommitBorrowed(borrowed) = record {
1271 *borrowed_metadata = BorrowedMetadata::Borrowed(borrowed);
1272 }
1273 }
1274 }
1275 JournalRecord::Discard(offset) => {
1276 if offset == 0 {
1277 bail!(
1278 anyhow!(FxfsError::Inconsistent)
1279 .context("Invalid offset for Discard")
1280 );
1281 }
1282 if let Some(transaction) = current_transaction.as_ref() {
1283 if transaction.checkpoint.file_offset < offset {
1284 continue;
1286 }
1287 }
1288 current_transaction = None;
1289 while let Some(transaction) = transactions.last() {
1290 if transaction.checkpoint.file_offset < offset {
1291 break;
1292 }
1293 transactions.pop();
1294 }
1295 reader.handle().discard_extents(offset);
1296 }
1297 JournalRecord::DidFlushDevice(offset) => {
1298 if offset > device_flushed_offset {
1299 device_flushed_offset = offset;
1300 }
1301 }
1302 },
1303
1304 ReadResult::ChecksumMismatch => break,
1306 }
1307 }
1308
1309 if current_transaction.is_some() {
1311 transactions.pop();
1312 }
1313
1314 Ok(JournaledTransactions { transactions, device_flushed_offset })
1315 }
1316
1317 pub async fn init_empty(&self, filesystem: Arc<FxFilesystem>) -> Result<(), Error> {
1320 const INIT_ROOT_PARENT_STORE_OBJECT_ID: u64 = 3;
1326 const INIT_ROOT_STORE_OBJECT_ID: u64 = 4;
1327 const INIT_ALLOCATOR_OBJECT_ID: u64 = 5;
1328
1329 info!(device_size = filesystem.device().size(); "Formatting");
1330
1331 let checkpoint = JournalCheckpoint {
1332 version: LATEST_VERSION,
1333 ..self.inner.lock().writer.journal_file_checkpoint()
1334 };
1335
1336 let mut current_generation = 1;
1337 if filesystem.options().image_builder_mode.is_some() {
1338 let block_size = filesystem.block_size();
1346 match self.read_superblocks(filesystem.device(), block_size).await {
1347 Ok((super_block, _)) => {
1348 log::info!(
1349 "Found existing superblock with generation {}. Bumping by 1.",
1350 super_block.generation
1351 );
1352 current_generation = super_block.generation.wrapping_add(1);
1353 }
1354 Err(_) => {
1355 }
1359 }
1360 }
1361
1362 let root_parent = ObjectStore::new_empty(
1363 None,
1364 INIT_ROOT_PARENT_STORE_OBJECT_ID,
1365 filesystem.clone(),
1366 None,
1367 );
1368 self.objects.set_root_parent_store(root_parent.clone());
1369
1370 let allocator = Arc::new(Allocator::new(filesystem.clone(), INIT_ALLOCATOR_OBJECT_ID));
1371 self.objects.set_allocator(allocator.clone());
1372 self.objects.init_metadata_reservation().await?;
1373
1374 let journal_handle;
1375 let super_block_a_handle;
1376 let super_block_b_handle;
1377 let root_store;
1378 let mut transaction = root_parent
1379 .new_transaction(
1380 lock_keys![],
1381 Options { skip_journal_checks: true, ..Default::default() },
1382 )
1383 .await?;
1384 root_store = root_parent
1385 .new_child_store(
1386 &mut transaction,
1387 NewChildStoreOptions { object_id: INIT_ROOT_STORE_OBJECT_ID, ..Default::default() },
1388 None,
1389 )
1390 .await
1391 .context("new_child_store")?;
1392 self.objects.set_root_store(root_store.clone());
1393
1394 allocator.create(&mut transaction).await?;
1395
1396 super_block_a_handle = ObjectStore::create_object_with_id(
1398 &root_store,
1399 &mut transaction,
1400 ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::A.object_id()).unwrap()),
1401 HandleOptions::default(),
1402 None,
1403 )
1404 .context("create super block")?;
1405 root_store.update_last_object_id(SuperBlockInstance::A.object_id());
1406 super_block_a_handle
1407 .extend(&mut transaction, SuperBlockInstance::A.first_extent())
1408 .await
1409 .context("extend super block")?;
1410 super_block_b_handle = ObjectStore::create_object_with_id(
1411 &root_store,
1412 &mut transaction,
1413 ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::B.object_id()).unwrap()),
1414 HandleOptions::default(),
1415 None,
1416 )
1417 .context("create super block")?;
1418 root_store.update_last_object_id(SuperBlockInstance::B.object_id());
1419 super_block_b_handle
1420 .extend(&mut transaction, SuperBlockInstance::B.first_extent())
1421 .await
1422 .context("extend super block")?;
1423
1424 journal_handle = ObjectStore::create_object(
1426 &root_parent,
1427 &mut transaction,
1428 journal_handle_options(),
1429 None,
1430 )
1431 .await
1432 .context("create journal")?;
1433 if self.inner.lock().image_builder_mode.is_none() {
1434 let mut file_range = 0..self.chunk_size();
1435 journal_handle
1436 .preallocate_range(&mut transaction, &mut file_range)
1437 .await
1438 .context("preallocate journal")?;
1439 if file_range.start < file_range.end {
1440 bail!("preallocate_range returned too little space");
1441 }
1442 }
1443
1444 root_store.create(&mut transaction).await?;
1446
1447 root_parent.set_graveyard_directory_object_id(
1449 Graveyard::create(&mut transaction, &root_parent).await?,
1450 );
1451
1452 transaction.commit().await?;
1453
1454 self.inner.lock().super_block_header = SuperBlockHeader::new(
1455 current_generation,
1456 root_parent.store_object_id(),
1457 root_parent.graveyard_directory_object_id(),
1458 root_store.store_object_id(),
1459 allocator.object_id(),
1460 journal_handle.object_id(),
1461 checkpoint,
1462 LATEST_VERSION,
1463 );
1464
1465 let _ = self.handle.set(journal_handle);
1467 Ok(())
1468 }
1469
1470 pub async fn finalize_image_builder(&self) -> Result<(), Error> {
1474 let handle = self.handle.get().unwrap();
1475 let mut transaction = handle
1476 .store()
1477 .new_transaction(
1478 lock_keys![LockKey::object(handle.store().store_object_id(), handle.object_id())],
1479 Options { skip_journal_checks: true, ..Default::default() },
1480 )
1481 .await?;
1482 let mut file_range = 0..self.chunk_size();
1483 handle
1484 .preallocate_range(&mut transaction, &mut file_range)
1485 .await
1486 .context("preallocate journal")?;
1487 if file_range.start < file_range.end {
1488 bail!("preallocate_range returned too little space");
1489 }
1490 transaction.commit().await?;
1491
1492 {
1493 let mut inner = self.inner.lock();
1494 inner.image_builder_mode = None;
1495 inner.compaction_state = CompactionState::Forced;
1496 }
1497 scopeguard::defer! {
1498 let mut inner = self.inner.lock();
1499 inner.compaction_state = CompactionState::Idle;
1500 if let Some(waker) = inner.flush_waker.take() {
1501 waker.wake();
1502 }
1503 self.reclaim_event.notify(usize::MAX);
1504 }
1505
1506 let earliest_version = self
1507 .objects
1508 .flush(FlushReason::Journal(ForceMajor::False))
1509 .await
1510 .context("Failed to flush objects")?;
1511 self.inner.lock().super_block_header.earliest_version = earliest_version;
1512
1513 let reservation = self.objects.metadata_reservation();
1519 let needed = self.objects.required_reservation().saturating_sub(reservation.amount());
1520 if needed > 0 {
1521 reservation.add(
1522 self.objects
1523 .allocator()
1524 .reserve(None, needed)
1525 .ok_or(FxfsError::NoSpace)
1526 .context("Failed to reserve metadata space in image builder mode")?
1527 .forget(),
1528 );
1529 }
1530
1531 self.write_super_block().await.context("Failed to write superblock")
1532 }
1533
1534 pub async fn init_superblocks(&self) -> Result<(), Error> {
1535 for _ in 0..2 {
1537 self.write_super_block().await?;
1538 }
1539 Ok(())
1540 }
1541
1542 pub async fn read_transactions_for_object(
1548 &self,
1549 object_id: u64,
1550 ) -> Result<Vec<JournaledTransaction>, Error> {
1551 let handle = self.handle.get().expect("No journal handle");
1552 let handle = ObjectStore::open_object(
1554 handle.owner(),
1555 handle.object_id(),
1556 journal_handle_options(),
1557 None,
1558 )
1559 .await?;
1560
1561 let checkpoint = match self.objects.journal_checkpoint(object_id) {
1562 Some(checkpoint) => checkpoint,
1563 None => return Ok(vec![]),
1564 };
1565 let mut reader = JournalReader::new(handle, &checkpoint);
1566 let end_offset = self.inner.lock().valid_to;
1569 Ok(self.read_transactions(&mut reader, Some(end_offset), object_id).await?.transactions)
1570 }
1571
1572 pub async fn commit(&self, transaction: &mut Transaction<'_>) -> Result<u64, Error> {
1574 if transaction.is_empty() {
1575 return Ok(self.inner.lock().writer.journal_file_checkpoint().file_offset);
1576 }
1577
1578 self.pre_commit(transaction).await?;
1579 Ok(self.write_and_apply_mutations(transaction))
1580 }
1581
1582 async fn pre_commit(&self, _transaction: &Transaction<'_>) -> Result<(), Error> {
1585 let handle;
1586
1587 let (size, zero_offset) = {
1588 let mut inner = self.inner.lock();
1589
1590 if std::mem::take(&mut inner.output_reset_version) {
1592 LATEST_VERSION.serialize_into(&mut inner.writer)?;
1593 }
1594
1595 if let Some(discard_offset) = inner.discard_offset {
1596 JournalRecord::Discard(discard_offset).serialize_into(&mut inner.writer)?;
1597 inner.discard_offset = None;
1598 }
1599
1600 if inner.needs_did_flush_device {
1601 let offset = inner.device_flushed_offset;
1602 JournalRecord::DidFlushDevice(offset).serialize_into(&mut inner.writer)?;
1603 inner.needs_did_flush_device = false;
1604 }
1605
1606 handle = match self.handle.get() {
1607 None => return Ok(()),
1608 Some(x) => x,
1609 };
1610
1611 let file_offset = inner.writer.journal_file_checkpoint().file_offset;
1612
1613 let size = handle.get_size();
1614 let size = if file_offset + self.chunk_size() > size { Some(size) } else { None };
1615
1616 if size.is_none()
1617 && inner.zero_offset.is_none()
1618 && !self.objects.needs_borrow_for_journal(file_offset)
1619 {
1620 return Ok(());
1621 }
1622
1623 (size, inner.zero_offset)
1624 };
1625
1626 let mut transaction = handle
1627 .new_transaction_with_options(Options {
1628 skip_journal_checks: true,
1629 reservation: ReservationOptions::Borrowed,
1630 ..Default::default()
1631 })
1632 .await?;
1633 if let Some(size) = size {
1634 handle
1635 .preallocate_range(&mut transaction, &mut (size..size + self.chunk_size()))
1636 .await?;
1637 }
1638 if let Some(zero_offset) = zero_offset {
1639 handle.zero(&mut transaction, 0..zero_offset).await?;
1640 }
1641
1642 self.write_and_apply_mutations(&mut transaction);
1645
1646 let mut inner = self.inner.lock();
1647
1648 if let Some(size) = size {
1651 assert!(inner.writer.journal_file_checkpoint().file_offset < size);
1652 }
1653
1654 if inner.zero_offset == zero_offset {
1655 inner.zero_offset = None;
1656 }
1657
1658 Ok(())
1659 }
1660
1661 fn should_apply(&self, object_id: u64, journal_file_checkpoint: &JournalCheckpoint) -> bool {
1665 let super_block_header = &self.inner.lock().super_block_header;
1666 let offset = super_block_header
1667 .journal_file_offsets
1668 .get(&object_id)
1669 .cloned()
1670 .unwrap_or(super_block_header.super_block_journal_file_offset);
1671 journal_file_checkpoint.file_offset >= offset
1672 }
1673
1674 async fn write_super_block(&self) -> Result<(), Error> {
1677 let root_parent_store = self.objects.root_parent_store();
1678
1679 let old_layers;
1684 let old_super_block_offset;
1685 let mut new_super_block_header;
1686 let checkpoint;
1687 let reserved_metadata_space;
1688
1689 {
1690 let _sync_guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1691 {
1692 let _write_guard = self.writer_mutex.lock();
1693 checkpoint = self.pad_to_block()?;
1694 reserved_metadata_space =
1695 self.objects.reconcile_metadata_reservation(checkpoint.file_offset);
1696 old_layers = super_block::compact_root_parent(&*root_parent_store)?;
1697 }
1698 self.flush_device(checkpoint.file_offset)
1699 .await
1700 .context("flush failed when writing superblock")?;
1701 }
1702
1703 new_super_block_header = self.inner.lock().super_block_header.clone();
1704
1705 old_super_block_offset = new_super_block_header.journal_checkpoint.file_offset;
1706
1707 let (journal_file_offsets, min_checkpoint) = self.objects.journal_file_offsets();
1708
1709 new_super_block_header.generation = new_super_block_header.generation.wrapping_add(1);
1710 new_super_block_header.super_block_journal_file_offset = checkpoint.file_offset;
1711 new_super_block_header.journal_checkpoint = min_checkpoint.unwrap_or(checkpoint);
1712 new_super_block_header.journal_checkpoint.version = LATEST_VERSION;
1713 new_super_block_header.journal_file_offsets = journal_file_offsets;
1714 new_super_block_header.reserved_metadata_space = reserved_metadata_space;
1715
1716 self.super_block_manager
1717 .save(
1718 new_super_block_header.clone(),
1719 self.objects.root_parent_store().filesystem(),
1720 old_layers,
1721 )
1722 .await?;
1723 {
1724 let mut inner = self.inner.lock();
1725 inner.super_block_header = new_super_block_header;
1726 inner.zero_offset = Some(BLOCK_SIZE.align_down(old_super_block_offset));
1727 }
1728
1729 Ok(())
1730 }
1731
1732 pub async fn sync(&self, options: SyncOptions<'_>) -> Result<Option<JournalCheckpoint>, Error> {
1738 let _guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1739
1740 let checkpoint = {
1741 if let Some(precondition) = options.precondition {
1742 if !precondition() {
1743 return Ok(None);
1744 }
1745 }
1746
1747 let _guard = self.writer_mutex.lock();
1750
1751 self.pad_to_block()?
1752 };
1753
1754 if options.flush_device {
1755 self.flush_device(checkpoint.file_offset).await.context("sync: flush failed")?;
1756 }
1757
1758 Ok(Some(checkpoint))
1759 }
1760
1761 fn pad_to_block(&self) -> Result<JournalCheckpoint, Error> {
1765 let mut inner = self.inner.lock();
1766 let checkpoint = inner.writer.journal_file_checkpoint();
1767 if checkpoint.file_offset % BLOCK_SIZE != 0 {
1768 JournalRecord::EndBlock.serialize_into(&mut inner.writer)?;
1769 inner.writer.pad_to_block()?;
1770 if let Some(waker) = inner.flush_waker.take() {
1771 waker.wake();
1772 }
1773 }
1774 Ok(checkpoint)
1775 }
1776
1777 async fn flush_device(&self, checkpoint_offset: u64) -> Result<(), Error> {
1778 assert!(
1779 self.inner.lock().image_builder_mode.is_none(),
1780 "flush_device called in image builder mode"
1781 );
1782 debug_assert_not_too_long!(poll_fn(|ctx| {
1783 let mut inner = self.inner.lock();
1784 if inner.flushed_offset >= checkpoint_offset {
1785 Poll::Ready(Ok(()))
1786 } else if inner.terminate {
1787 let context = inner
1788 .terminate_reason
1789 .as_ref()
1790 .map(|e| format!("Journal closed with error: {:?}", e))
1791 .unwrap_or_else(|| "Journal closed".to_string());
1792 Poll::Ready(Err(anyhow!(FxfsError::JournalFlushError).context(context)))
1793 } else {
1794 inner.sync_waker = Some(ctx.waker().clone());
1795 Poll::Pending
1796 }
1797 }))?;
1798
1799 let needs_flush = self.inner.lock().device_flushed_offset < checkpoint_offset;
1800 if needs_flush {
1801 let trace = self.trace.load(Ordering::Relaxed);
1802 if trace {
1803 info!("J: start flush device");
1804 }
1805 self.handle.get().unwrap().flush_device().await?;
1806 if trace {
1807 info!("J: end flush device");
1808 }
1809
1810 {
1818 let mut inner = self.inner.lock();
1819 inner.device_flushed_offset = checkpoint_offset;
1820 inner.needs_did_flush_device = true;
1821 }
1822
1823 self.objects.allocator().did_flush_device(checkpoint_offset);
1826 if trace {
1827 info!("J: did flush device");
1828 }
1829 }
1830
1831 Ok(())
1832 }
1833
1834 pub fn super_block_header(&self) -> SuperBlockHeader {
1836 self.inner.lock().super_block_header.clone()
1837 }
1838
1839 pub async fn check_journal_space(&self) -> Result<(), Error> {
1841 loop {
1842 let listener = {
1843 let inner = self.inner.lock();
1844 if inner.terminate {
1845 let context = inner
1848 .terminate_reason
1849 .as_ref()
1850 .map(|e| format!("Journal closed with error: {:?}", e))
1851 .unwrap_or_else(|| "Journal closed".to_string());
1852 return Err(anyhow!(FxfsError::JournalFlushError).context(context));
1853 }
1854 if self.objects.last_end_offset()
1855 - inner.super_block_header.journal_checkpoint.file_offset
1856 < inner.reclaim_size
1857 {
1858 return Ok(());
1859 }
1860 if inner.image_builder_mode.is_some() {
1861 return Ok(());
1862 }
1863 if inner.disable_compactions {
1864 return Err(
1865 anyhow!(FxfsError::JournalFlushError).context("Compactions disabled")
1866 );
1867 }
1868 self.reclaim_event.listen()
1869 };
1870 self.hooks.on_waiting_for_journal_space();
1871 debug_assert_not_too_long!(listener);
1872 }
1873 }
1874
1875 fn chunk_size(&self) -> u64 {
1876 CHUNK_SIZE
1877 }
1878
1879 fn write_and_apply_mutations(&self, transaction: &mut Transaction<'_>) -> u64 {
1880 let checkpoint_before;
1881 let checkpoint_after;
1882 let mut object_usages = SmallVec::<[(u64, u64); 4]>::new();
1883 {
1884 let _guard = self.writer_mutex.lock();
1885 checkpoint_before = {
1886 let mut inner = self.inner.lock();
1887 if transaction.includes_write() {
1888 inner.needs_barrier = true;
1889 }
1890 let checkpoint = inner.writer.journal_file_checkpoint();
1891 let mut iter = transaction.mutations().iter().peekable();
1892 while let Some(store_iter) = ObjectMutationIterator::new(&mut iter) {
1893 let object_id = store_iter.object_id();
1894 let mut usage = 0;
1895 self.objects.write_mutations(
1896 object_id,
1897 store_iter,
1898 Writer { object_id, writer: &mut inner.writer, usage: &mut usage },
1899 );
1900 if usage > 0 {
1901 object_usages.push((object_id, usage));
1902 }
1903 }
1904 checkpoint
1905 };
1906 self.objects.apply_transaction(transaction, &checkpoint_before).expect(
1907 "apply_transaction should not fail in live mode; \
1908 filesystem will be in an inconsistent state",
1909 );
1910 checkpoint_after = {
1911 let mut inner = self.inner.lock();
1912 for (owner_object_id, device_range, checksums, first_write) in
1913 transaction.take_checksums().into_iter()
1914 {
1915 inner
1916 .writer
1917 .write_record(&JournalRecord::DataChecksums(
1918 owner_object_id,
1919 device_range,
1920 Checksums::fletcher(checksums),
1921 first_write,
1922 ))
1923 .unwrap();
1924 }
1925 let commit_record = if transaction.metadata_reservation.is_borrowed() {
1926 JournalRecord::CommitBorrowed(self.objects.metadata_reservation().amount())
1927 } else {
1928 JournalRecord::Commit
1929 };
1930 inner.writer.write_record(&commit_record).unwrap();
1931
1932 inner.writer.journal_file_checkpoint()
1933 };
1934 }
1935 self.objects.did_commit_transaction(
1936 transaction,
1937 checkpoint_after.file_offset,
1938 &object_usages,
1939 );
1940
1941 if let Some(waker) = self.inner.lock().flush_waker.take() {
1942 waker.wake();
1943 }
1944
1945 checkpoint_before.file_offset
1946 }
1947
1948 pub async fn flush_task(self: Arc<Self>) {
1952 let mut flush_fut = None;
1953 let mut compact_fut = None;
1954 let mut flush_error = false;
1955 poll_fn(|ctx| {
1956 loop {
1957 {
1958 let mut inner = self.inner.lock();
1959 if flush_fut.is_none() && !flush_error && self.handle.get().is_some() {
1960 let flushable = inner.writer.flushable_bytes();
1961 if flushable > 0 {
1962 flush_fut = Some(Box::pin(self.flush(flushable)));
1963 }
1964 }
1965 if inner.terminate && flush_fut.is_none() && compact_fut.is_none() {
1966 return Poll::Ready(());
1967 }
1968 let journal_bytes = self.objects.last_end_offset()
1973 - inner.super_block_header.journal_checkpoint.file_offset;
1974 fxfs_trace::counter!("journal-bytes", 0, "total" => journal_bytes);
1975 if compact_fut.is_none()
1979 && !inner.compaction_state.is_running()
1980 && !inner.terminate
1981 && !inner.disable_compactions
1982 && !inner.compactions_paused
1983 && inner.image_builder_mode.is_none()
1984 && journal_bytes > inner.reclaim_size / 2
1985 {
1986 compact_fut = Some(Box::pin(self.compact()));
1987 inner.compaction_state = CompactionState::Running;
1988 }
1989 inner.flush_waker = Some(ctx.waker().clone());
1990 }
1991 let mut pending = true;
1992 if let Some(fut) = flush_fut.as_mut() {
1993 if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1994 if let Err(e) = result {
1995 self.inner.lock().terminate(Some(e.context("Flush error")));
1996 self.reclaim_event.notify(usize::MAX);
1997 flush_error = true;
1998 }
1999 flush_fut = None;
2000 pending = false;
2001 }
2002 }
2003 if let Some(fut) = compact_fut.as_mut() {
2004 if let Poll::Ready(result) = fut.poll_unpin(ctx) {
2005 let mut inner = self.inner.lock();
2006 if let Err(e) = result {
2007 inner.terminate(Some(e.context("Compaction error")));
2008 }
2009 compact_fut = None;
2010 inner.compaction_state = CompactionState::Idle;
2011 self.reclaim_event.notify(usize::MAX);
2012 pending = false;
2013 fxfs_trace::counter!(
2014 "journal-bytes",
2015 0,
2016 "total" => self.objects.last_end_offset()
2017 - inner.super_block_header.journal_checkpoint.file_offset
2018 );
2019 }
2020 }
2021 if pending {
2022 return Poll::Pending;
2023 }
2024 }
2025 })
2026 .await;
2027 }
2028
2029 pub fn get_compaction_yielder(&self) -> CompactionYielder<'_> {
2031 CompactionYielder::new(self)
2032 }
2033
2034 async fn flush(&self, amount: usize) -> Result<(), Error> {
2035 let handle = self.handle.get().unwrap();
2036 let mut buf = handle.allocate_buffer(amount).await;
2037 let (offset, len, barrier_on_first_write) = {
2038 let mut inner = self.inner.lock();
2039 let offset = inner.writer.take_flushable(buf.as_mut());
2040 let barrier_on_first_write = inner.needs_barrier && inner.barriers_enabled;
2041 inner.needs_barrier = false;
2044 (offset, buf.len() as u64, barrier_on_first_write)
2045 };
2046 self.handle
2047 .get()
2048 .unwrap()
2049 .overwrite(
2050 offset,
2051 buf.as_mut(),
2052 OverwriteOptions { barrier_on_first_write, ..Default::default() },
2053 )
2054 .await?;
2055
2056 let mut inner = self.inner.lock();
2057 if let Some(waker) = inner.sync_waker.take() {
2058 waker.wake();
2059 }
2060 inner.flushed_offset = offset + len;
2061 inner.valid_to = inner.flushed_offset;
2062 Ok(())
2063 }
2064
2065 #[trace]
2066 async fn compact(&self) -> Result<(), Error> {
2067 assert!(
2068 self.inner.lock().image_builder_mode.is_none(),
2069 "compact called in image builder mode"
2070 );
2071 let bytes_before = self.objects.compaction_bytes_written();
2072 let _measure = crate::metrics::DurationMeasureScope::new(
2073 &crate::metrics::lsm_tree_metrics().journal_compaction_time,
2074 );
2075 crate::metrics::lsm_tree_metrics().journal_compactions_total.add(1);
2076 let trace = self.trace.load(Ordering::Relaxed);
2077 debug!("Compaction starting");
2078 if trace {
2079 info!("J: start compaction");
2080 }
2081 let earliest_version = self
2082 .objects
2083 .flush(FlushReason::Journal(ForceMajor::False))
2084 .await
2085 .context("Failed to flush objects")?;
2086 self.inner.lock().super_block_header.earliest_version = earliest_version;
2087 self.write_super_block().await.context("Failed to write superblock")?;
2088 if trace {
2089 info!("J: end compaction");
2090 }
2091 debug!("Compaction finished");
2092 let bytes_after = self.objects.compaction_bytes_written();
2093 crate::metrics::lsm_tree_metrics()
2094 .journal_compaction_bytes_written
2095 .add(bytes_after.saturating_sub(bytes_before));
2096 Ok(())
2097 }
2098
2099 pub async fn force_compact(&self) -> Result<(), Error> {
2102 loop {
2103 debug_assert_not_too_long!({
2104 let mut inner = self.inner.lock();
2105 if !inner.compaction_state.is_running() {
2106 inner.compaction_state = CompactionState::Forced;
2107 break;
2108 }
2109 self.reclaim_event.listen()
2110 });
2111 }
2112 scopeguard::defer! {
2113 let mut inner = self.inner.lock();
2114 inner.compaction_state = CompactionState::Idle;
2115 if let Some(waker) = inner.flush_waker.take() {
2116 waker.wake();
2117 }
2118 self.reclaim_event.notify(usize::MAX);
2119 }
2120 self.compact().await
2121 }
2122
2123 pub async fn stop_compactions(&self) {
2124 loop {
2125 debug_assert_not_too_long!({
2126 let mut inner = self.inner.lock();
2127 inner.disable_compactions = true;
2128 if !inner.compaction_state.is_running() {
2129 return;
2130 }
2131 self.reclaim_event.listen()
2132 });
2133 }
2134 }
2135
2136 pub async fn pause_compactions(&self) {
2137 loop {
2138 debug_assert_not_too_long!({
2139 let mut inner = self.inner.lock();
2140 inner.compactions_paused = true;
2141 if !inner.compaction_state.is_running() {
2142 return;
2143 }
2144 self.reclaim_event.listen()
2145 });
2146 }
2147 }
2148
2149 pub fn resume_compactions(&self) {
2150 let mut inner = self.inner.lock();
2151 inner.compactions_paused = false;
2152 if let Some(waker) = inner.flush_waker.take() {
2153 waker.wake();
2154 }
2155 }
2156
2157 pub fn track_statistics(self: &Arc<Self>, parent: &fuchsia_inspect::Node, name: &str) {
2160 let this = Arc::downgrade(self);
2161 parent.record_lazy_child(name, move || {
2162 let this_clone = this.clone();
2163 async move {
2164 let inspector = fuchsia_inspect::Inspector::default();
2165 if let Some(this) = this_clone.upgrade() {
2166 let (journal_min, journal_max, journal_reclaim_size) = {
2167 let inner = this.inner.lock();
2169 (
2170 BLOCK_SIZE.align_down(
2171 inner.super_block_header.journal_checkpoint.file_offset,
2172 ),
2173 inner.flushed_offset,
2174 inner.reclaim_size,
2175 )
2176 };
2177 let root = inspector.root();
2178 root.record_uint("journal_min_offset", journal_min);
2179 root.record_uint("journal_max_offset", journal_max);
2180 root.record_uint("journal_size", journal_max - journal_min);
2181 root.record_uint("journal_reclaim_size", journal_reclaim_size);
2182
2183 if let Some(x) = round_div(
2185 100 * (journal_max - journal_min),
2186 this.objects.allocator().get_disk_bytes(),
2187 ) {
2188 root.record_uint("journal_size_to_disk_size_percent", x);
2189 }
2190 }
2191 Ok(inspector)
2192 }
2193 .boxed()
2194 });
2195 }
2196
2197 pub fn terminate(&self) {
2199 self.inner.lock().terminate(None);
2200 self.reclaim_event.notify(usize::MAX);
2201 }
2202}
2203
2204pub struct Writer<'a> {
2206 object_id: u64,
2207 writer: &'a mut JournalWriter,
2208 usage: &'a mut u64,
2209}
2210
2211impl Writer<'_> {
2212 pub fn write(&mut self, mutation: Mutation) {
2213 let count_usage = mutation.counts_towards_object_journal_usage();
2214 let offset_before = self.writer.journal_file_checkpoint().file_offset;
2215 self.writer
2216 .write_record(&JournalRecord::Mutation { object_id: self.object_id, mutation })
2217 .unwrap();
2218 if count_usage {
2219 *self.usage += self.writer.journal_file_checkpoint().file_offset - offset_before;
2220 }
2221 }
2222}
2223
2224#[cfg(target_os = "fuchsia")]
2225mod yielder {
2226 use super::{CompactionState, Journal};
2227 use crate::lsm_tree::Yielder;
2228 use fuchsia_async as fasync;
2229
2230 pub struct CompactionYielder<'a> {
2234 journal: &'a Journal,
2235 low_priority_task: Option<fasync::LowPriorityTask>,
2236 }
2237
2238 impl<'a> CompactionYielder<'a> {
2239 pub fn new(journal: &'a Journal) -> Self {
2240 Self { journal, low_priority_task: None }
2241 }
2242 }
2243
2244 impl Yielder for CompactionYielder<'_> {
2245 async fn yield_now(&mut self) {
2246 const IDLE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(4);
2250 const MAX_YIELD_DURATION: zx::MonotonicDuration =
2251 zx::MonotonicDuration::from_millis(16);
2252
2253 {
2254 let inner = self.journal.inner.lock();
2255 if inner.compaction_state == CompactionState::Forced {
2256 return;
2257 }
2258 let outstanding = self.journal.objects.last_end_offset()
2259 - inner.super_block_header.journal_checkpoint.file_offset;
2260 let half_reclaim_size = inner.reclaim_size / 2;
2261 if outstanding
2262 .checked_sub(half_reclaim_size)
2263 .is_some_and(|x| x >= half_reclaim_size / 2)
2264 {
2265 self.low_priority_task = None;
2269 return;
2270 }
2271 }
2272
2273 self.low_priority_task
2274 .get_or_insert_with(|| fasync::LowPriorityTask::new())
2275 .wait_until_idle_for(
2276 IDLE_PERIOD,
2277 fasync::MonotonicInstant::after(MAX_YIELD_DURATION),
2278 )
2279 .await;
2280 }
2281 }
2282}
2283
2284#[cfg(not(target_os = "fuchsia"))]
2285mod yielder {
2286 use super::Journal;
2287 use crate::lsm_tree::Yielder;
2288
2289 #[expect(dead_code)]
2290 pub struct CompactionYielder<'a>(&'a Journal);
2291
2292 impl<'a> CompactionYielder<'a> {
2293 pub fn new(journal: &'a Journal) -> Self {
2294 Self(journal)
2295 }
2296 }
2297
2298 impl Yielder for CompactionYielder<'_> {
2299 async fn yield_now(&mut self) {}
2300 }
2301}
2302
2303pub use yielder::*;
2304
2305#[cfg(test)]
2306mod tests {
2307 use super::{JournalRecord, JournalRecordV59, SuperBlockInstance};
2308 use crate::checksum::Checksums;
2309 use crate::errors::FxfsError;
2310 use crate::filesystem::{
2311 FlushReason, ForceMajor, FxFilesystem, FxFilesystemBuilder, SyncOptions,
2312 };
2313 use crate::fsck::fsck;
2314 use crate::object_handle::{
2315 INVALID_OBJECT_ID, ObjectHandle, ReadObjectHandle, WriteObjectHandle,
2316 };
2317 use crate::object_store::directory::{Directory, replace_child};
2318 use crate::object_store::object_record::{ObjectKey, ObjectValue};
2319 use crate::object_store::transaction::{Mutation, Options, ReservationOptions};
2320 use crate::object_store::volume::root_volume;
2321 use crate::object_store::{
2322 HandleOptions, LockKey, NewChildStoreOptions, ObjectStore, StoreOptions, lock_keys,
2323 };
2324 use crate::serialized_types::Version;
2325 #[cfg(target_os = "fuchsia")]
2326 use fuchsia_async::TestExecutor;
2327 use fuchsia_async::{self as fasync, MonotonicDuration};
2328 use fxfs_insecure_crypto::new_insecure_crypt;
2329 use rustc_hash::FxHashMap as HashMap;
2330 use std::sync::Arc;
2331 use storage_device::DeviceHolder;
2332 use storage_device::fake_device::FakeDevice;
2333
2334 const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
2335
2336 #[fuchsia::test]
2337 async fn test_replay() {
2338 const TEST_DATA: &[u8] = b"hello";
2339
2340 let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2341
2342 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2343
2344 let object_id = {
2345 let root_store = fs.root_store();
2346 let root_directory =
2347 Directory::open(&root_store, root_store.root_directory_object_id())
2348 .await
2349 .expect("open failed");
2350 let mut transaction = fs
2351 .root_store()
2352 .new_transaction(
2353 lock_keys![LockKey::object(
2354 root_store.store_object_id(),
2355 root_store.root_directory_object_id(),
2356 )],
2357 Options::default(),
2358 )
2359 .await
2360 .expect("new_transaction failed");
2361 let handle = root_directory
2362 .create_child_file(&mut transaction, "test")
2363 .await
2364 .expect("create_child_file failed");
2365
2366 transaction.commit().await.expect("commit failed");
2367 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2368 buf.copy_from_slice(TEST_DATA);
2369 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2370 fs.sync(SyncOptions::default()).await.expect("sync failed");
2373 handle.object_id()
2374 };
2375
2376 {
2377 fs.close().await.expect("Close failed");
2378 let device = fs.take_device().await;
2379 device.reopen(false);
2380 let fs = FxFilesystem::open(device).await.expect("open failed");
2381 let handle = ObjectStore::open_object(
2382 &fs.root_store(),
2383 object_id,
2384 HandleOptions::default(),
2385 None,
2386 )
2387 .await
2388 .expect("open_object failed");
2389 let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2390 assert_eq!(
2391 handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2392 TEST_DATA.len()
2393 );
2394 assert_eq!(buf.subslice(..TEST_DATA.len()).to_vec(), TEST_DATA);
2395 fsck(fs.clone()).await.expect("fsck failed");
2396 fs.close().await.expect("Close failed");
2397 }
2398 }
2399
2400 #[fuchsia::test]
2401 async fn test_reset() {
2402 const TEST_DATA: &[u8] = b"hello";
2403
2404 let device = DeviceHolder::new(FakeDevice::new(32768, TEST_DEVICE_BLOCK_SIZE));
2405
2406 let mut object_ids = Vec::new();
2407
2408 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2409 {
2410 let root_store = fs.root_store();
2411 let root_directory =
2412 Directory::open(&root_store, root_store.root_directory_object_id())
2413 .await
2414 .expect("open failed");
2415 let mut transaction = fs
2416 .root_store()
2417 .new_transaction(
2418 lock_keys![LockKey::object(
2419 root_store.store_object_id(),
2420 root_store.root_directory_object_id(),
2421 )],
2422 Options::default(),
2423 )
2424 .await
2425 .expect("new_transaction failed");
2426 let handle = root_directory
2427 .create_child_file(&mut transaction, "test")
2428 .await
2429 .expect("create_child_file failed");
2430 transaction.commit().await.expect("commit failed");
2431 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2432 buf.copy_from_slice(TEST_DATA);
2433 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2434 fs.sync(SyncOptions::default()).await.expect("sync failed");
2435 object_ids.push(handle.object_id());
2436
2437 for i in 0..1000 {
2440 let mut transaction = fs
2441 .root_store()
2442 .new_transaction(
2443 lock_keys![LockKey::object(
2444 root_store.store_object_id(),
2445 root_store.root_directory_object_id(),
2446 )],
2447 Options::default(),
2448 )
2449 .await
2450 .expect("new_transaction failed");
2451 let handle = root_directory
2452 .create_child_file(&mut transaction, &format!("{}", i))
2453 .await
2454 .expect("create_child_file failed");
2455 transaction.commit().await.expect("commit failed");
2456 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2457 buf.copy_from_slice(TEST_DATA);
2458 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2459 object_ids.push(handle.object_id());
2460 }
2461 }
2462 fs.close().await.expect("fs close failed");
2463 let device = fs.take_device().await;
2464 device.reopen(false);
2465 let fs = FxFilesystem::open(device).await.expect("open failed");
2466 fsck(fs.clone()).await.expect("fsck failed");
2467 {
2468 let root_store = fs.root_store();
2469 for &object_id in &object_ids[0..1] {
2471 let handle = ObjectStore::open_object(
2472 &root_store,
2473 object_id,
2474 HandleOptions::default(),
2475 None,
2476 )
2477 .await
2478 .expect("open_object failed");
2479 let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2480 assert_eq!(
2481 handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2482 TEST_DATA.len()
2483 );
2484 let data = buf.subslice(..TEST_DATA.len()).to_vec();
2485 assert_eq!(data, TEST_DATA);
2486 }
2487
2488 let root_directory =
2490 Directory::open(&root_store, root_store.root_directory_object_id())
2491 .await
2492 .expect("open failed");
2493 let mut transaction = fs
2494 .root_store()
2495 .new_transaction(
2496 lock_keys![LockKey::object(
2497 root_store.store_object_id(),
2498 root_store.root_directory_object_id(),
2499 )],
2500 Options::default(),
2501 )
2502 .await
2503 .expect("new_transaction failed");
2504 let handle = root_directory
2505 .create_child_file(&mut transaction, "test2")
2506 .await
2507 .expect("create_child_file failed");
2508 transaction.commit().await.expect("commit failed");
2509 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2510 buf.copy_from_slice(TEST_DATA);
2511 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2512 fs.sync(SyncOptions::default()).await.expect("sync failed");
2513 object_ids.push(handle.object_id());
2514 }
2515
2516 fs.close().await.expect("close failed");
2517 let device = fs.take_device().await;
2518 device.reopen(false);
2519 let fs = FxFilesystem::open(device).await.expect("open failed");
2520 {
2521 fsck(fs.clone()).await.expect("fsck failed");
2522
2523 for &object_id in object_ids[0..1].iter().chain(object_ids.last().cloned().iter()) {
2525 let handle = ObjectStore::open_object(
2526 &fs.root_store(),
2527 object_id,
2528 HandleOptions::default(),
2529 None,
2530 )
2531 .await
2532 .unwrap_or_else(|e| {
2533 panic!("open_object failed (object_id: {}): {:?}", object_id, e)
2534 });
2535 let mut buf = handle.allocate_buffer(handle.block_size().get() as usize).await;
2536 assert_eq!(
2537 handle.read_aligned(0, buf.as_mut()).await.expect("read failed"),
2538 TEST_DATA.len()
2539 );
2540 let data = buf.subslice(..TEST_DATA.len()).to_vec();
2541 assert_eq!(data, TEST_DATA);
2542 }
2543 }
2544 fs.close().await.expect("close failed");
2545 }
2546
2547 #[fuchsia::test]
2548 async fn test_discard() {
2549 let device = {
2550 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2551 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2552 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2553
2554 let store = root_volume
2555 .new_volume("test", NewChildStoreOptions::default())
2556 .await
2557 .expect("new_volume failed");
2558 let root_directory = Directory::open(&store, store.root_directory_object_id())
2559 .await
2560 .expect("open failed");
2561
2562 let mut i = 0;
2564 loop {
2565 let mut transaction = fs
2566 .root_store()
2567 .new_transaction(
2568 lock_keys![LockKey::object(
2569 store.store_object_id(),
2570 store.root_directory_object_id()
2571 )],
2572 Options::default(),
2573 )
2574 .await
2575 .expect("new_transaction failed");
2576 root_directory
2577 .create_child_file(&mut transaction, &format!("a {i}"))
2578 .await
2579 .expect("create_child_file failed");
2580 if transaction.commit().await.expect("commit failed") > super::CHUNK_SIZE {
2581 break;
2582 }
2583 i += 1;
2584 }
2585
2586 fs.journal().force_compact().await.expect("compact failed");
2588 fs.journal().stop_compactions().await;
2589
2590 let mut i = 0;
2592 loop {
2593 let mut transaction = fs
2594 .root_store()
2595 .new_transaction(
2596 lock_keys![LockKey::object(
2597 store.store_object_id(),
2598 store.root_directory_object_id()
2599 )],
2600 Options::default(),
2601 )
2602 .await
2603 .expect("new_transaction failed");
2604 root_directory
2605 .create_child_file(&mut transaction, &format!("b {i}"))
2606 .await
2607 .expect("create_child_file failed");
2608 if transaction.commit().await.expect("commit failed") > 2 * super::CHUNK_SIZE {
2609 break;
2610 }
2611 i += 1;
2612 }
2613
2614 fasync::Timer::new(MonotonicDuration::from_millis(10)).await;
2616 fs.device().snapshot().expect("snapshot failed")
2619 };
2620
2621 let fs = FxFilesystem::open(device).await.expect("open failed");
2622
2623 {
2624 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2625
2626 let store =
2627 root_volume.volume("test", StoreOptions::default()).await.expect("volume failed");
2628
2629 let root_directory = Directory::open(&store, store.root_directory_object_id())
2630 .await
2631 .expect("open failed");
2632
2633 let mut transaction = fs
2635 .root_store()
2636 .new_transaction(
2637 lock_keys![LockKey::object(
2638 store.store_object_id(),
2639 store.root_directory_object_id()
2640 )],
2641 Options::default(),
2642 )
2643 .await
2644 .expect("new_transaction failed");
2645 root_directory
2646 .create_child_file(&mut transaction, &format!("d"))
2647 .await
2648 .expect("create_child_file failed");
2649 transaction.commit().await.expect("commit failed");
2650 }
2651
2652 fs.close().await.expect("close failed");
2653 let device = fs.take_device().await;
2654 device.reopen(false);
2655
2656 let fs = FxFilesystem::open(device).await.expect("open failed");
2657 fsck(fs.clone()).await.expect("fsck failed");
2658 fs.close().await.expect("close failed");
2659 }
2660
2661 #[fuchsia::test]
2662 async fn test_use_existing_generation() {
2663 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2664
2665 let fs = FxFilesystemBuilder::new()
2667 .format(true)
2668 .image_builder_mode(Some(SuperBlockInstance::A))
2669 .open(device)
2670 .await
2671 .expect("open failed");
2672 fs.enable_allocations();
2673 let generation0 = fs.super_block_header().generation;
2674 assert_eq!(generation0, 1);
2675 fs.close().await.expect("close failed");
2676 let device = fs.take_device().await;
2677 device.reopen(false);
2678
2679 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2681 let generation1 = fs.super_block_header().generation;
2682 {
2683 let root_volume = crate::object_store::volume::root_volume(fs.clone())
2684 .await
2685 .expect("root_volume failed");
2686 root_volume
2687 .new_volume("test", crate::object_store::NewChildStoreOptions::default())
2688 .await
2689 .expect("new_volume failed");
2690 }
2691 fs.close().await.expect("close failed");
2692 let device = fs.take_device().await;
2693 device.reopen(false);
2694
2695 let fs = FxFilesystemBuilder::new()
2697 .format(true)
2698 .image_builder_mode(Some(SuperBlockInstance::A))
2699 .open(device)
2700 .await
2701 .expect("open failed");
2702 fs.enable_allocations();
2703 let generation2 = fs.super_block_header().generation;
2704 assert!(
2705 generation2 > generation1,
2706 "generation2 ({}) should be greater than generation1 ({})",
2707 generation2,
2708 generation1
2709 );
2710 fs.close().await.expect("close failed");
2711 }
2712
2713 #[fuchsia::test]
2714 async fn test_image_builder_mode_generation_bump_512_byte_block() {
2715 let device = DeviceHolder::new(FakeDevice::new(16384, 512));
2716
2717 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2719 let generation1 = fs.super_block_header().generation;
2720 fs.close().await.expect("close failed");
2721 let device = fs.take_device().await;
2722 device.reopen(false);
2723
2724 let fs = FxFilesystemBuilder::new()
2726 .format(true)
2727 .image_builder_mode(Some(SuperBlockInstance::A))
2728 .open(device)
2729 .await
2730 .expect("open failed");
2731
2732 fs.enable_allocations();
2733 let generation2 = fs.super_block_header().generation;
2734 assert!(
2735 generation2 > generation1,
2736 "Expected generation bump, got {} vs {}",
2737 generation2,
2738 generation1
2739 );
2740 fs.close().await.expect("close failed");
2741 }
2742
2743 #[fuchsia::test]
2744 #[cfg(target_os = "fuchsia")]
2745 fn test_low_priority_compaction() {
2746 let mut executor = TestExecutor::new_with_fake_time();
2747 let mut fut = std::pin::pin!(async {
2748 use std::sync::Arc;
2749 use std::sync::atomic::{AtomicBool, Ordering};
2750
2751 let device = DeviceHolder::new(FakeDevice::new(16384, TEST_DEVICE_BLOCK_SIZE));
2752 let fs = FxFilesystemBuilder::new()
2753 .journal_options(super::JournalOptions {
2754 reclaim_size: 65536,
2755 ..Default::default()
2756 })
2757 .format(true)
2758 .open(device)
2759 .await
2760 .expect("open failed");
2761
2762 let _low = fasync::LowPriorityTask::new();
2763
2764 {
2766 let root_store = fs.root_store();
2767 let root_directory =
2768 Directory::open(&root_store, root_store.root_directory_object_id())
2769 .await
2770 .expect("open failed");
2771 for i in 0..100 {
2772 let mut transaction = fs
2773 .root_store()
2774 .new_transaction(
2775 lock_keys![LockKey::object(
2776 root_store.store_object_id(),
2777 root_store.root_directory_object_id(),
2778 )],
2779 Options::default(),
2780 )
2781 .await
2782 .expect("new_transaction failed");
2783 root_directory
2784 .create_child_file(&mut transaction, &format!("test{}", i))
2785 .await
2786 .expect("create_child_file failed");
2787 transaction.commit().await.expect("commit failed");
2788 }
2789 }
2790
2791 let stop = Arc::new(AtomicBool::new(false));
2793 let stop_clone = stop.clone();
2794 let _normal_task = fasync::Task::spawn(async move {
2795 while !stop_clone.load(Ordering::Relaxed) {
2796 fasync::Timer::new(fasync::MonotonicInstant::after(
2797 MonotonicDuration::from_millis(1),
2798 ))
2799 .await;
2800 }
2801 });
2802
2803 {
2806 let root_store = fs.root_store();
2807 let root_directory =
2808 Directory::open(&root_store, root_store.root_directory_object_id())
2809 .await
2810 .expect("open failed");
2811 let mut i = 0;
2812 loop {
2813 let mut transaction = fs
2814 .root_store()
2815 .new_transaction(
2816 lock_keys![LockKey::object(
2817 root_store.store_object_id(),
2818 root_store.root_directory_object_id(),
2819 )],
2820 Options::default(),
2821 )
2822 .await
2823 .expect("new_transaction failed");
2824 root_directory
2825 .create_child_file(&mut transaction, &format!("trigger{i}"))
2826 .await
2827 .expect("create_child_file failed");
2828 transaction.commit().await.expect("commit failed");
2829
2830 if fs.journal().inner.lock().compaction_state.is_running() {
2831 break;
2832 }
2833 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2834 MonotonicDuration::from_millis(1),
2835 ))
2836 .await;
2837 i += 1;
2838 }
2839 }
2840
2841 for _ in 0..10 {
2844 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2845 MonotonicDuration::from_millis(1),
2846 ))
2847 .await;
2848 assert!(fs.journal().inner.lock().compaction_state.is_running());
2849 }
2850
2851 stop.store(true, Ordering::Relaxed);
2853 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2854 MonotonicDuration::from_millis(1),
2855 ))
2856 .await;
2857
2858 assert!(fs.journal().inner.lock().compaction_state.is_running());
2861
2862 for _ in 0..3 {
2864 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2865 MonotonicDuration::from_millis(1),
2866 ))
2867 .await;
2868 assert!(fs.journal().inner.lock().compaction_state.is_running());
2869 }
2870
2871 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2873 MonotonicDuration::from_millis(1),
2874 ))
2875 .await;
2876
2877 let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2879 assert!(!fs.journal().inner.lock().compaction_state.is_running());
2880
2881 fs.close().await.expect("Close failed");
2882 });
2883 assert!(executor.run_until_stalled(&mut fut).is_ready());
2884 }
2885
2886 #[fuchsia::test]
2887 #[cfg(target_os = "fuchsia")]
2888 fn test_low_priority_compaction_deadline() {
2889 let mut executor = TestExecutor::new_with_fake_time();
2890 let mut fut = std::pin::pin!(async {
2891 use std::sync::Arc;
2892 use std::sync::atomic::{AtomicBool, Ordering};
2893
2894 let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2895 let fs = FxFilesystemBuilder::new()
2896 .journal_options(super::JournalOptions {
2897 reclaim_size: 65536,
2898 ..Default::default()
2899 })
2900 .format(true)
2901 .open(device)
2902 .await
2903 .expect("open failed");
2904
2905 let _low = fasync::LowPriorityTask::new();
2906
2907 {
2909 let root_store = fs.root_store();
2910 let root_directory =
2911 Directory::open(&root_store, root_store.root_directory_object_id())
2912 .await
2913 .expect("open failed");
2914 for i in 0..10 {
2915 let mut transaction = fs
2916 .root_store()
2917 .new_transaction(
2918 lock_keys![LockKey::object(
2919 root_store.store_object_id(),
2920 root_store.root_directory_object_id(),
2921 )],
2922 Options::default(),
2923 )
2924 .await
2925 .expect("new_transaction failed");
2926 root_directory
2927 .create_child_file(&mut transaction, &format!("test{}", i))
2928 .await
2929 .expect("create_child_file failed");
2930 transaction.commit().await.expect("commit failed");
2931 }
2932 }
2933
2934 let stop = Arc::new(AtomicBool::new(false));
2936 let stop_clone = stop.clone();
2937 let _normal_task = fasync::Task::spawn(async move {
2938 while !stop_clone.load(Ordering::Relaxed) {
2939 fasync::Timer::new(fasync::MonotonicInstant::after(
2940 MonotonicDuration::from_millis(3),
2941 ))
2942 .await;
2943 }
2944 });
2945
2946 {
2948 let root_store = fs.root_store();
2949 let root_directory =
2950 Directory::open(&root_store, root_store.root_directory_object_id())
2951 .await
2952 .expect("open failed");
2953 let mut i = 0;
2954 loop {
2955 let mut transaction = fs
2956 .root_store()
2957 .new_transaction(
2958 lock_keys![LockKey::object(
2959 root_store.store_object_id(),
2960 root_store.root_directory_object_id(),
2961 )],
2962 Options::default(),
2963 )
2964 .await
2965 .expect("new_transaction failed");
2966 root_directory
2967 .create_child_file(&mut transaction, &format!("trigger{i}"))
2968 .await
2969 .expect("create_child_file failed");
2970 transaction.commit().await.expect("commit failed");
2971
2972 if fs.journal().inner.lock().compaction_state.is_running() {
2973 break;
2974 }
2975 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2976 MonotonicDuration::from_millis(1),
2977 ))
2978 .await;
2979 i += 1;
2980 }
2981 }
2982
2983 let mut count = 0;
2986 for _ in 0..1000 {
2987 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2988 MonotonicDuration::from_millis(20),
2989 ))
2990 .await;
2991 if !fs.journal().inner.lock().compaction_state.is_running() {
2992 break;
2993 }
2994 count += 1;
2995 }
2996 assert!(!fs.journal().inner.lock().compaction_state.is_running());
2997
2998 assert!(count > 200);
3001
3002 stop.store(true, Ordering::Relaxed);
3003 fs.close().await.expect("Close failed");
3004 });
3005 assert!(executor.run_until_stalled(&mut fut).is_ready());
3006 }
3007
3008 #[fuchsia::test]
3009 #[cfg(target_os = "fuchsia")]
3010 fn test_low_priority_compaction_no_yielding_when_full() {
3011 let mut executor = TestExecutor::new_with_fake_time();
3012 let mut fut = std::pin::pin!(async {
3013 use std::sync::Arc;
3014 use std::sync::atomic::{AtomicBool, Ordering};
3015
3016 let reclaim_size = 65536;
3017 let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
3018 let fs = FxFilesystemBuilder::new()
3019 .journal_options(super::JournalOptions { reclaim_size, ..Default::default() })
3020 .format(true)
3021 .open(device)
3022 .await
3023 .expect("open failed");
3024
3025 let _low = fasync::LowPriorityTask::new();
3026
3027 {
3029 let root_store = fs.root_store();
3030 let root_directory =
3031 Directory::open(&root_store, root_store.root_directory_object_id())
3032 .await
3033 .expect("open failed");
3034 for i in 0..10 {
3035 let mut transaction = fs
3036 .root_store()
3037 .new_transaction(
3038 lock_keys![LockKey::object(
3039 root_store.store_object_id(),
3040 root_store.root_directory_object_id(),
3041 )],
3042 Options::default(),
3043 )
3044 .await
3045 .expect("new_transaction failed");
3046 root_directory
3047 .create_child_file(&mut transaction, &format!("test{}", i))
3048 .await
3049 .expect("create_child_file failed");
3050 transaction.commit().await.expect("commit failed");
3051 }
3052 }
3053
3054 let stop = Arc::new(AtomicBool::new(false));
3056 let stop_clone = stop.clone();
3057 let _normal_task = fasync::Task::spawn(async move {
3058 while !stop_clone.load(Ordering::Relaxed) {
3059 fasync::Timer::new(fasync::MonotonicInstant::after(
3060 MonotonicDuration::from_millis(3),
3061 ))
3062 .await;
3063 }
3064 });
3065
3066 {
3068 let root_store = fs.root_store();
3069 let root_directory =
3070 Directory::open(&root_store, root_store.root_directory_object_id())
3071 .await
3072 .expect("open failed");
3073 let mut i = 0;
3074 loop {
3075 let mut transaction = fs
3076 .root_store()
3077 .new_transaction(
3078 lock_keys![LockKey::object(
3079 root_store.store_object_id(),
3080 root_store.root_directory_object_id(),
3081 )],
3082 Options::default(),
3083 )
3084 .await
3085 .expect("new_transaction failed");
3086 root_directory
3087 .create_child_file(&mut transaction, &format!("trigger{i}"))
3088 .await
3089 .expect("create_child_file failed");
3090 transaction.commit().await.expect("commit failed");
3091
3092 let outstanding = {
3093 let inner = fs.journal().inner.lock();
3094 fs.journal().objects.last_end_offset()
3095 - inner.super_block_header.journal_checkpoint.file_offset
3096 };
3097 if outstanding >= reclaim_size * 7 / 8 {
3098 break;
3099 }
3100 i += 1;
3102 }
3103 }
3104
3105 TestExecutor::advance_to(fasync::MonotonicInstant::after(
3107 MonotonicDuration::from_millis(4),
3108 ))
3109 .await;
3110
3111 let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
3113 assert!(!fs.journal().inner.lock().compaction_state.is_running());
3114
3115 stop.store(true, Ordering::Relaxed);
3116 fs.close().await.expect("Close failed");
3117 });
3118 assert!(executor.run_until_stalled(&mut fut).is_ready());
3119 }
3120
3121 #[fuchsia::test]
3122 async fn test_data_checksums_validation() {
3123 {
3126 let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3127 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3128 fs.close().await.expect("close failed");
3129 let device = fs.take_device().await;
3130 device.reopen(false);
3131
3132 let fs = FxFilesystem::open(device).await.expect("open failed");
3133 let _ = fs
3134 .root_store()
3135 .new_transaction(lock_keys![], Options::default())
3136 .await
3137 .expect("new_transaction failed");
3138 {
3139 let mut inner = fs.journal().inner.lock();
3140 assert!(std::mem::take(&mut inner.output_reset_version));
3141 Version { major: 59, minor: 0 }.serialize_into(&mut inner.writer).unwrap();
3142 inner
3143 .writer
3144 .write_record(&JournalRecordV59::DataChecksums(
3145 512..1024,
3146 Checksums::fletcher(vec![0]),
3147 false,
3148 ))
3149 .unwrap();
3150 inner.writer.write_record(&JournalRecordV59::Commit).unwrap();
3151 }
3152 fs.close().await.expect("close failed");
3153 let device = fs.take_device().await;
3154 device.reopen(false);
3155
3156 let fs = FxFilesystem::open(device).await.expect("open V59 DataChecksums failed");
3157 fs.close().await.expect("close failed");
3158 }
3159
3160 {
3162 let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3163 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3164 {
3165 let mut inner = fs.journal().inner.lock();
3166 inner
3167 .writer
3168 .write_record(&JournalRecord::DataChecksums(
3169 INVALID_OBJECT_ID,
3170 512..1024,
3171 Checksums::fletcher(vec![0]),
3172 false,
3173 ))
3174 .unwrap();
3175 inner.writer.write_record(&JournalRecord::Commit).unwrap();
3176 }
3177 fs.close().await.expect("close failed");
3178 let device = fs.take_device().await;
3179 device.reopen(false);
3180
3181 let err = FxFilesystem::open(device)
3182 .await
3183 .err()
3184 .expect("expected INVALID_OBJECT_ID in V60+ DataChecksums to fail");
3185 assert!(FxfsError::Inconsistent.matches(&err), "unexpected error: {err:?}");
3186 }
3187
3188 {
3190 let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
3191 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3192 {
3193 let mut inner = fs.journal().inner.lock();
3194 inner
3195 .writer
3196 .write_record(&JournalRecord::Mutation {
3197 object_id: 99,
3198 mutation: Mutation::DeleteVolume,
3199 })
3200 .unwrap();
3201 inner.writer.write_record(&JournalRecord::Commit).unwrap();
3202 inner
3203 .writer
3204 .write_record(&JournalRecord::DataChecksums(
3205 99,
3206 512..1024,
3207 Checksums::fletcher(vec![0]),
3208 false,
3209 ))
3210 .unwrap();
3211 inner.writer.write_record(&JournalRecord::Commit).unwrap();
3212 }
3213 fs.close().await.expect("close failed");
3214 let device = fs.take_device().await;
3215 device.reopen(false);
3216
3217 let err = FxFilesystem::open(device)
3218 .await
3219 .err()
3220 .expect("expected DataChecksums for deleted store to fail");
3221 assert!(FxfsError::Inconsistent.matches(&err), "unexpected error: {err:?}");
3222 }
3223 }
3224
3225 #[fuchsia::test]
3226 async fn test_delete_volume_reused_extents_before_did_flush_device() {
3227 let device = DeviceHolder::new(FakeDevice::new(4096, TEST_DEVICE_BLOCK_SIZE));
3228 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3229 let root_vol = root_volume(fs.clone()).await.expect("root_volume failed");
3230
3231 let vol1 =
3234 root_vol.new_volume("vol1", NewChildStoreOptions::default()).await.expect("new_volume");
3235 let vol2 =
3236 root_vol.new_volume("vol2", NewChildStoreOptions::default()).await.expect("new_volume");
3237 let vol2_root =
3238 Directory::open(&vol2, vol2.root_directory_object_id()).await.expect("open failed");
3239 let mut transaction = fs
3240 .root_store()
3241 .new_transaction(
3242 lock_keys![LockKey::object(
3243 vol2.store_object_id(),
3244 vol2.root_directory_object_id()
3245 )],
3246 Options::default(),
3247 )
3248 .await
3249 .expect("new_transaction failed");
3250 let bar =
3251 vol2_root.create_child_file(&mut transaction, "bar").await.expect("create_child_file");
3252 transaction.commit().await.expect("commit failed");
3253
3254 {
3256 let vol1_root =
3257 Directory::open(&vol1, vol1.root_directory_object_id()).await.expect("open failed");
3258 let mut transaction = fs
3259 .root_store()
3260 .new_transaction(
3261 lock_keys![LockKey::object(
3262 vol1.store_object_id(),
3263 vol1.root_directory_object_id()
3264 )],
3265 Options::default(),
3266 )
3267 .await
3268 .expect("new_transaction failed");
3269 let file = vol1_root
3270 .create_child_file(&mut transaction, "file")
3271 .await
3272 .expect("create_child_file");
3273 transaction.commit().await.expect("commit failed");
3274 let mut buf = file.allocate_buffer(65536).await;
3275 buf.fill(0xaa);
3276 file.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3277 }
3278 drop(vol1);
3279
3280 let (_, transaction) = root_vol
3282 .acquire_transaction_for_remove_volume("vol1", [], false, Options::default())
3283 .await
3284 .expect("acquire_transaction_for_remove_volume failed");
3285 root_vol.delete_volume("vol1", transaction, || {}).await.expect("delete_volume failed");
3286
3287 let mut buf = bar.allocate_buffer(65536).await;
3293 buf.fill(0xbb);
3294 bar.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3295
3296 let snapshot = fs.device().snapshot().expect("snapshot failed");
3298 let fs2 = FxFilesystem::open(snapshot).await.expect("open snapshot failed");
3299 fsck(fs2.clone()).await.expect("fsck failed");
3300 let root_vol2 = root_volume(fs2.clone()).await.expect("root_volume failed");
3301 root_vol2
3302 .volume("vol1", StoreOptions::default())
3303 .await
3304 .err()
3305 .expect("vol1 should remain deleted");
3306 fs2.close().await.expect("close failed");
3307 fs.close().await.expect("close failed");
3308 }
3309
3310 #[fuchsia::test]
3311 async fn test_metadata_reservation_replay_and_reserved_space_change() {
3312 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
3313 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3314 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3315 let store = root_volume
3316 .new_volume("test", NewChildStoreOptions::default())
3317 .await
3318 .expect("new_volume failed");
3319 let store_id = store.store_object_id();
3320 let root_directory =
3321 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
3322
3323 let other_store = root_volume
3324 .new_volume("other", NewChildStoreOptions::default())
3325 .await
3326 .expect("new_volume failed");
3327 let other_store_id = other_store.store_object_id();
3328 let other_root_directory =
3329 Directory::open(&other_store, other_store.root_directory_object_id())
3330 .await
3331 .expect("open failed");
3332
3333 let object_id = {
3334 let mut transaction = fs
3335 .root_store()
3336 .new_transaction(
3337 lock_keys![LockKey::object(store_id, store.root_directory_object_id())],
3338 Options::default(),
3339 )
3340 .await
3341 .expect("new_transaction failed");
3342 let handle = root_directory
3343 .create_child_file(&mut transaction, "to_delete")
3344 .await
3345 .expect("create_child_file failed");
3346 transaction.commit().await.expect("commit failed");
3347 let mut buf = handle.allocate_buffer(4096).await;
3348 buf.fill(0xab);
3349 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3350 handle.object_id()
3351 };
3352
3353 {
3354 let mut transaction = fs
3355 .root_store()
3356 .new_transaction(
3357 lock_keys![LockKey::object(
3358 other_store_id,
3359 other_store.root_directory_object_id()
3360 )],
3361 Options::default(),
3362 )
3363 .await
3364 .expect("new_transaction failed");
3365 other_root_directory
3366 .create_child_file(&mut transaction, "other_file")
3367 .await
3368 .expect("create_child_file failed");
3369 transaction.commit().await.expect("commit failed");
3370 }
3371
3372 fs.journal().force_compact().await.expect("force_compact failed");
3374 fs.journal().stop_compactions().await;
3375
3376 let (store_borrowed_after_compact, store_req_after_compact) =
3377 fs.object_manager().store_borrowed(store_id).unwrap();
3378 assert_eq!(store_borrowed_after_compact, 0);
3379 assert!(store_req_after_compact > 0);
3380 let (other_borrowed_after_compact, other_req_after_compact) =
3381 fs.object_manager().store_borrowed(other_store_id).unwrap();
3382 assert_eq!(other_borrowed_after_compact, 0);
3383 assert!(other_req_after_compact > 0);
3384
3385 {
3388 let mut transaction = fs
3389 .root_store()
3390 .new_transaction(
3391 lock_keys![LockKey::object(store_id, store.root_directory_object_id())],
3392 Options::default(),
3393 )
3394 .await
3395 .expect("new_transaction failed");
3396 root_directory
3397 .create_child_file(&mut transaction, "keep")
3398 .await
3399 .expect("create_child_file failed");
3400 transaction.commit().await.expect("commit failed");
3401
3402 let mut transaction = fs
3403 .root_store()
3404 .new_transaction(
3405 lock_keys![
3406 LockKey::object(store_id, store.root_directory_object_id()),
3407 LockKey::object(store_id, object_id),
3408 ],
3409 Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3410 )
3411 .await
3412 .expect("new_transaction failed");
3413 replace_child(&mut transaction, None, (&root_directory, "to_delete"))
3414 .await
3415 .expect("replace_child failed");
3416 transaction.commit().await.expect("commit failed");
3417
3418 fs.tombstone_object(store_id, object_id, None).await.expect("tombstone_object failed");
3419 }
3420
3421 fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3422 .await
3423 .expect("sync failed");
3424
3425 let expected_reservation = fs.object_manager().metadata_reservation().amount();
3426 let expected_borrowed = fs.object_manager().borrowed_metadata_space();
3427 let expected_reserved_map = fs.object_manager().reserved_metadata_space();
3428 assert!(expected_borrowed > 0);
3429
3430 let (expected_store_borrowed, expected_store_req) =
3431 fs.object_manager().store_borrowed(store_id).unwrap();
3432 assert!(expected_store_borrowed > 0 && expected_store_borrowed < expected_store_req);
3433 let (expected_other_borrowed, expected_other_req) =
3434 fs.object_manager().store_borrowed(other_store_id).unwrap();
3435 assert_eq!(expected_other_borrowed, 0);
3436
3437 let snapshot1 = fs.device().snapshot().expect("snapshot failed");
3438 let snapshot2 = fs.device().snapshot().expect("snapshot failed");
3439 let snapshot3 = fs.device().snapshot().expect("snapshot failed");
3440 fs.close().await.expect("close failed");
3441
3442 {
3445 let fs2 = FxFilesystem::open(snapshot1).await.expect("open failed");
3446 assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
3447 assert_eq!(fs2.object_manager().borrowed_metadata_space(), expected_borrowed);
3448 assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_reserved_map);
3449 assert_eq!(
3450 fs2.object_manager().store_borrowed(store_id),
3451 Some((expected_store_borrowed, expected_store_req))
3452 );
3453 assert_eq!(
3454 fs2.object_manager().store_borrowed(other_store_id),
3455 Some((expected_other_borrowed, expected_other_req))
3456 );
3457 fsck(fs2.clone()).await.expect("fsck failed");
3458 fs2.close().await.expect("close failed");
3459 }
3460
3461 {
3465 let extra = 65536;
3466 let fs2 = FxFilesystemBuilder::new()
3467 .on_new_store(move |store| {
3468 store
3469 .filesystem()
3470 .ensure_local()
3471 .object_manager()
3472 .set_reserved_space(super::RESERVED_SPACE + extra);
3473 })
3474 .open(snapshot2)
3475 .await
3476 .expect("open failed");
3477 assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
3478 assert_eq!(fs2.object_manager().borrowed_metadata_space(), expected_borrowed + extra);
3479 assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_reserved_map);
3480 fsck(fs2.clone()).await.expect("fsck failed");
3481 fs2.close().await.expect("close failed");
3482 }
3483
3484 {
3487 let fs2 = FxFilesystemBuilder::new()
3488 .on_new_store(move |store| {
3489 store.filesystem().ensure_local().object_manager().set_reserved_space(0);
3490 })
3491 .open(snapshot3)
3492 .await
3493 .expect("open failed");
3494 assert_eq!(
3495 fs2.object_manager().metadata_reservation().amount(),
3496 fs2.object_manager().required_reservation()
3497 );
3498 assert_eq!(fs2.object_manager().borrowed_metadata_space(), 0);
3499 fsck(fs2.clone()).await.expect("fsck failed");
3500 fs2.close().await.expect("close failed");
3501 }
3502 }
3503
3504 #[fuchsia::test]
3505 async fn test_borrowing_between_begin_and_end_flush_and_replay() {
3506 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
3507 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3508 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3509 let store_a = root_volume
3510 .new_volume("store_a", NewChildStoreOptions::default())
3511 .await
3512 .expect("new_volume failed");
3513 let store_a_id = store_a.store_object_id();
3514 let dir_a = Directory::open(&store_a, store_a.root_directory_object_id())
3515 .await
3516 .expect("open failed");
3517
3518 let store_b = root_volume
3519 .new_volume("store_b", NewChildStoreOptions::default())
3520 .await
3521 .expect("new_volume failed");
3522 let store_b_id = store_b.store_object_id();
3523 let dir_b = Directory::open(&store_b, store_b.root_directory_object_id())
3524 .await
3525 .expect("open failed");
3526
3527 let mut file_ids = Vec::new();
3528 for name in ["file1", "file2"] {
3529 let mut transaction = fs
3530 .root_store()
3531 .new_transaction(
3532 lock_keys![LockKey::object(store_a_id, store_a.root_directory_object_id())],
3533 Options::default(),
3534 )
3535 .await
3536 .expect("new_transaction failed");
3537 let handle = dir_a
3538 .create_child_file(&mut transaction, name)
3539 .await
3540 .expect("create_child_file failed");
3541 transaction.commit().await.expect("commit failed");
3542 let mut buf = handle.allocate_buffer(4096).await;
3543 buf.fill(0xcd);
3544 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
3545 file_ids.push(handle.object_id());
3546 }
3547
3548 fs.journal().force_compact().await.expect("force_compact failed");
3549 fs.journal().stop_compactions().await;
3550
3551 {
3553 let mut transaction = fs
3554 .root_store()
3555 .new_transaction(
3556 lock_keys![
3557 LockKey::object(store_a_id, store_a.root_directory_object_id()),
3558 LockKey::object(store_a_id, file_ids[0]),
3559 ],
3560 Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3561 )
3562 .await
3563 .expect("new_transaction failed");
3564 replace_child(&mut transaction, None, (&dir_a, "file1"))
3565 .await
3566 .expect("replace_child failed");
3567 transaction.commit().await.expect("commit failed");
3568 fs.tombstone_object(store_a_id, file_ids[0], None)
3569 .await
3570 .expect("tombstone_object failed");
3571 }
3572
3573 let (pre_borrowed, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3574 assert!(pre_borrowed > 0);
3575
3576 let store_info_lock = fs
3579 .lock_manager()
3580 .write_lock(lock_keys![LockKey::object(
3581 fs.root_store().store_object_id(),
3582 store_a.store_info_handle_object_id().unwrap(),
3583 )])
3584 .await;
3585
3586 let store_a_clone = store_a.clone();
3587 let flush_task = fasync::Task::spawn(async move {
3588 store_a_clone.flush().await.expect("store_a flush failed");
3589 });
3590
3591 while !store_a.tree().immutable_layer_set().layers.iter().any(|l| l.handle().is_none()) {
3593 fasync::Timer::new(MonotonicDuration::from_millis(1)).await;
3594 }
3595
3596 {
3599 let mut transaction = fs
3600 .root_store()
3601 .new_transaction(
3602 lock_keys![
3603 LockKey::object(store_a_id, store_a.root_directory_object_id()),
3604 LockKey::object(store_a_id, file_ids[1]),
3605 ],
3606 Options { reservation: ReservationOptions::Borrowed, ..Default::default() },
3607 )
3608 .await
3609 .expect("new_transaction failed");
3610 replace_child(&mut transaction, None, (&dir_a, "file2"))
3611 .await
3612 .expect("replace_child failed");
3613 transaction.commit().await.expect("commit failed");
3614 fs.tombstone_object(store_a_id, file_ids[1], None)
3615 .await
3616 .expect("tombstone_object failed");
3617
3618 let mut transaction = fs
3619 .root_store()
3620 .new_transaction(
3621 lock_keys![LockKey::object(store_b_id, store_b.root_directory_object_id())],
3622 Options::default(),
3623 )
3624 .await
3625 .expect("new_transaction failed");
3626 dir_b
3627 .create_child_file(&mut transaction, "b_file")
3628 .await
3629 .expect("create_child_file failed");
3630 transaction.commit().await.expect("commit failed");
3631 }
3632
3633 let (total_borrowed_mid, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3634 let post_borrowed = total_borrowed_mid - pre_borrowed;
3635 assert!(post_borrowed > 0);
3636
3637 std::mem::drop(store_info_lock);
3639 flush_task.await;
3640
3641 let (b_a_after, req_a_after) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3644 assert_eq!(b_a_after, post_borrowed);
3645 let (b_b_after, req_b_after) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3646 assert_eq!(b_b_after, 0);
3647
3648 fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3651 .await
3652 .expect("sync failed");
3653 let expected_map_before_sb = fs.object_manager().reserved_metadata_space();
3654 let snapshot_before_sb = fs.device().snapshot().expect("snapshot failed");
3655
3656 fs.journal().write_super_block().await.expect("write_super_block failed");
3661 {
3662 let mut transaction = fs
3663 .root_store()
3664 .new_transaction(
3665 lock_keys![LockKey::object(store_a_id, store_a.root_directory_object_id())],
3666 Options::default(),
3667 )
3668 .await
3669 .expect("new_transaction failed");
3670 dir_a
3671 .create_child_file(&mut transaction, "after_sb")
3672 .await
3673 .expect("create_child_file failed");
3674 transaction.commit().await.expect("commit failed");
3675 }
3676 fs.sync(SyncOptions { flush_device: true, ..Default::default() })
3677 .await
3678 .expect("sync failed");
3679 let expected_map_after_sb = fs.object_manager().reserved_metadata_space();
3680 let expected_store_a_after_sb = fs.object_manager().store_borrowed(store_a_id);
3681 let expected_store_b_after_sb = fs.object_manager().store_borrowed(store_b_id);
3682 let snapshot_after_sb = fs.device().snapshot().expect("snapshot failed");
3683 fs.close().await.expect("close failed");
3684
3685 {
3686 let fs2 = FxFilesystem::open(snapshot_before_sb).await.expect("open failed");
3687 assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_map_before_sb);
3688 assert_eq!(
3689 fs2.object_manager().store_borrowed(store_a_id),
3690 Some((b_a_after, req_a_after))
3691 );
3692 assert_eq!(
3693 fs2.object_manager().store_borrowed(store_b_id),
3694 Some((b_b_after, req_b_after))
3695 );
3696 fsck(fs2.clone()).await.expect("fsck failed");
3697 fs2.close().await.expect("close failed");
3698 }
3699
3700 {
3701 let fs3 = FxFilesystem::open(snapshot_after_sb).await.expect("open failed");
3702 assert_eq!(fs3.object_manager().reserved_metadata_space(), expected_map_after_sb);
3703 assert_eq!(fs3.object_manager().store_borrowed(store_a_id), expected_store_a_after_sb);
3704 assert_eq!(fs3.object_manager().store_borrowed(store_b_id), expected_store_b_after_sb);
3705 fsck(fs3.clone()).await.expect("fsck failed");
3706 fs3.close().await.expect("close failed");
3707 }
3708 }
3709
3710 async fn populate_store_with_base_layer(store: &ObjectStore) {
3711 let store_id = store.store_object_id();
3712 let dir_id = store.root_directory_object_id();
3713
3714 for batch in 0..28 {
3717 let mut transaction = store
3718 .new_transaction(lock_keys![LockKey::object(store_id, dir_id)], Options::default())
3719 .await
3720 .expect("new_transaction failed");
3721 for i in 0..50 {
3722 let idx = batch * 50 + i;
3723 transaction.add(
3724 store_id,
3725 Mutation::replace_or_insert_object(
3726 ObjectKey::extended_attribute(
3727 dir_id,
3728 format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
3729 ),
3730 ObjectValue::inline_extended_attribute(vec![0x55; 200]),
3731 ),
3732 );
3733 }
3734 transaction.commit().await.expect("commit failed");
3735 }
3736 store
3737 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
3738 .await
3739 .expect("flush failed");
3740 }
3741
3742 #[fuchsia::test]
3743 async fn test_per_store_selective_major_compaction_isolation() {
3744 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3745 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3746 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3747
3748 let store_a = root_volume
3749 .new_volume("store_a", NewChildStoreOptions::default())
3750 .await
3751 .expect("new_volume failed");
3752 let store_a_id = store_a.store_object_id();
3753 let root_dir_a = store_a.root_directory_object_id();
3754
3755 let store_b = root_volume
3756 .new_volume("store_b", NewChildStoreOptions::default())
3757 .await
3758 .expect("new_volume failed");
3759 let store_b_id = store_b.store_object_id();
3760 let root_dir_b = store_b.root_directory_object_id();
3761
3762 populate_store_with_base_layer(&store_a).await;
3763 populate_store_with_base_layer(&store_b).await;
3764 fs.journal().force_compact().await.expect("force_compact failed");
3765 fs.journal().stop_compactions().await;
3766 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
3767 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 1);
3768
3769 {
3771 let mut transaction = store_b
3772 .new_transaction(
3773 lock_keys![LockKey::object(store_b_id, root_dir_b)],
3774 Options::default(),
3775 )
3776 .await
3777 .expect("new_transaction failed");
3778 transaction.add(
3779 store_b_id,
3780 Mutation::replace_or_insert_object(
3781 ObjectKey::extended_attribute(root_dir_b, b"extra".to_vec()),
3782 ObjectValue::inline_extended_attribute(vec![0x77; 200]),
3783 ),
3784 );
3785 transaction.commit().await.expect("commit failed");
3786 store_b
3787 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
3788 .await
3789 .expect("minor flush failed");
3790 }
3791 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3792 let (b_b_before, req_b_before) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3793 assert_eq!(b_b_before, 0);
3794
3795 for batch in 0..28 {
3797 let mut transaction = store_a
3798 .new_transaction(
3799 lock_keys![LockKey::object(store_a_id, root_dir_a)],
3800 Options {
3801 skip_journal_checks: true,
3802 reservation: ReservationOptions::Borrowed,
3803 ..Default::default()
3804 },
3805 )
3806 .await
3807 .expect("new_transaction failed");
3808 for i in 0..50 {
3809 let idx = batch * 50 + i;
3810 transaction.add(
3811 store_a_id,
3812 Mutation::replace_or_insert_object(
3813 ObjectKey::extended_attribute(
3814 root_dir_a,
3815 format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
3816 ),
3817 ObjectValue::None,
3818 ),
3819 );
3820 }
3821 transaction.commit().await.expect("commit failed");
3822 let (b_a, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3823 if b_a >= req_a - req_a / 2 {
3824 break;
3825 }
3826 }
3827 let (b_a_depleted, req_a_depleted) =
3828 fs.object_manager().store_borrowed(store_a_id).unwrap();
3829 assert!(b_a_depleted >= req_a_depleted - req_a_depleted / 2);
3830
3831 assert_eq!(fs.object_manager().store_borrowed(store_b_id), Some((0, req_b_before)));
3833
3834 fs.object_manager()
3838 .flush(FlushReason::Journal(ForceMajor::False))
3839 .await
3840 .expect("flush failed");
3841 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
3842 let (b_a_restored, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3843 assert_eq!(b_a_restored, 0);
3844 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3845
3846 fsck(fs.clone()).await.expect("fsck failed");
3847 fs.close().await.expect("close failed");
3848 }
3849
3850 #[fuchsia::test]
3851 async fn test_shortfall_major_compaction_does_not_cycle() {
3852 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3853 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3854 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3855
3856 let store_b = root_volume
3857 .new_volume("store_b", NewChildStoreOptions::default())
3858 .await
3859 .expect("new_volume failed");
3860 let store_b_id = store_b.store_object_id();
3861 let root_dir_b = store_b.root_directory_object_id();
3862
3863 populate_store_with_base_layer(&store_b).await;
3864 fs.journal().force_compact().await.expect("force_compact failed");
3865 fs.journal().stop_compactions().await;
3866
3867 {
3869 let mut transaction = store_b
3870 .new_transaction(
3871 lock_keys![LockKey::object(store_b_id, root_dir_b)],
3872 Options::default(),
3873 )
3874 .await
3875 .expect("new_transaction failed");
3876 transaction.add(
3877 store_b_id,
3878 Mutation::replace_or_insert_object(
3879 ObjectKey::extended_attribute(root_dir_b, b"extra".to_vec()),
3880 ObjectValue::inline_extended_attribute(vec![0x77; 200]),
3881 ),
3882 );
3883 transaction.commit().await.expect("commit failed");
3884 store_b
3885 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
3886 .await
3887 .expect("minor flush failed");
3888 }
3889 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
3890 let (_, req_b_before) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3891
3892 let current_total = fs.object_manager().metadata_reservation().amount();
3895 let reduction = current_total / 2;
3896 fs.object_manager().metadata_reservation().give_back(reduction);
3897 fs.object_manager().set_store_borrowed(store_b_id, req_b_before);
3898
3899 fs.journal().force_compact().await.expect("force_compact failed");
3902 let info_b_after_major = store_b.load_store_info().await.unwrap();
3903 assert_eq!(info_b_after_major.layers.len(), 1);
3904 let (b_b_restored, _) = fs.object_manager().store_borrowed(store_b_id).unwrap();
3905 assert_eq!(b_b_restored, 0);
3906
3907 {
3912 let mut transaction = store_b
3913 .new_transaction(
3914 lock_keys![LockKey::object(store_b_id, root_dir_b)],
3915 Options::default(),
3916 )
3917 .await
3918 .expect("new_transaction failed");
3919 transaction.add(
3920 store_b_id,
3921 Mutation::replace_or_insert_object(
3922 ObjectKey::extended_attribute(root_dir_b, b"after_shortfall".to_vec()),
3923 ObjectValue::inline_extended_attribute(vec![0x88; 200]),
3924 ),
3925 );
3926 transaction.commit().await.expect("commit failed");
3927 }
3928 fs.object_manager()
3929 .flush(FlushReason::Journal(ForceMajor::False))
3930 .await
3931 .expect("flush failed");
3932 assert_eq!(
3933 store_b.load_store_info().await.unwrap().layers.len(),
3934 2,
3935 "Single transaction after shortfall major compaction must not trigger major compaction"
3936 );
3937
3938 fs.allocator()
3939 .reserve(None, reduction)
3940 .unwrap()
3941 .move_to(fs.object_manager().metadata_reservation(), reduction);
3942 fsck(fs.clone()).await.expect("fsck failed");
3943 fs.close().await.expect("close failed");
3944 }
3945
3946 #[fuchsia::test]
3947 async fn test_no_op_major_compaction_guard() {
3948 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3949 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3950 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3951
3952 let store_a = root_volume
3953 .new_volume("store_a", NewChildStoreOptions::default())
3954 .await
3955 .expect("new_volume failed");
3956 let store_a_id = store_a.store_object_id();
3957
3958 populate_store_with_base_layer(&store_a).await;
3959 fs.journal().force_compact().await.expect("force_compact failed");
3960 fs.journal().stop_compactions().await;
3961
3962 let layer_oid_a_before = store_a.load_store_info().await.unwrap().layers[0];
3966 let (_, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
3967 fs.object_manager().set_store_borrowed(store_a_id, req_a);
3968 fs.object_manager()
3969 .flush(FlushReason::Journal(ForceMajor::False))
3970 .await
3971 .expect("flush failed");
3972 assert_eq!(store_a.load_store_info().await.unwrap().layers, vec![layer_oid_a_before]);
3973
3974 fs.object_manager().set_store_borrowed(store_a_id, 0);
3975 fsck(fs.clone()).await.expect("fsck failed");
3976 fs.close().await.expect("close failed");
3977 }
3978
3979 #[fuchsia::test]
3980 async fn test_minor_compaction_replay_and_delete_volume_prunes_borrowed() {
3981 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
3982 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
3983 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
3984
3985 let store_b = root_volume
3986 .new_volume("store_b", NewChildStoreOptions::default())
3987 .await
3988 .expect("new_volume failed");
3989 let store_b_id = store_b.store_object_id();
3990 let root_dir_b = store_b.root_directory_object_id();
3991
3992 populate_store_with_base_layer(&store_b).await;
3993 fs.journal().force_compact().await.expect("force_compact failed");
3994 fs.journal().stop_compactions().await;
3995
3996 {
4002 let mut transaction = store_b
4003 .new_transaction(
4004 lock_keys![LockKey::object(store_b_id, root_dir_b)],
4005 Options {
4006 skip_journal_checks: true,
4007 reservation: ReservationOptions::Borrowed,
4008 ..Default::default()
4009 },
4010 )
4011 .await
4012 .expect("new_transaction failed");
4013 transaction.add(
4014 store_b_id,
4015 Mutation::replace_or_insert_object(
4016 ObjectKey::extended_attribute(root_dir_b, b"attr_0000_0".to_vec()),
4017 ObjectValue::None,
4018 ),
4019 );
4020 transaction.commit().await.expect("commit failed");
4021 }
4022 let (b_b_minor, _) = fs.object_manager().store_borrowed(store_b_id).unwrap();
4023 assert!(b_b_minor > 0);
4024 store_b
4025 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4026 .await
4027 .expect("minor flush failed");
4028 assert!(store_b.load_store_info().await.unwrap().layers.len() > 1);
4029 assert_eq!(fs.object_manager().store_borrowed(store_b_id).unwrap().0, b_b_minor);
4030
4031 let store_c = root_volume
4032 .new_volume("store_c", NewChildStoreOptions::default())
4033 .await
4034 .expect("new_volume failed");
4035 let store_c_id = store_c.store_object_id();
4036 std::mem::drop(store_c);
4037 let (_, delete_txn) = root_volume
4038 .acquire_transaction_for_remove_volume("store_c", [], false, Options::default())
4039 .await
4040 .expect("acquire_transaction_for_remove_volume failed");
4041 root_volume
4042 .delete_volume("store_c", delete_txn, || {})
4043 .await
4044 .expect("delete_volume failed");
4045 assert!(fs.object_manager().store_borrowed(store_c_id).is_none());
4046
4047 fs.sync(SyncOptions { flush_device: true, ..Default::default() })
4048 .await
4049 .expect("sync failed");
4050 let expected_map = fs.object_manager().reserved_metadata_space();
4051 assert!(!expected_map.contains_key(&store_c_id));
4052 let snapshot = fs.device().snapshot().expect("snapshot failed");
4053 fs.close().await.expect("close failed");
4054
4055 let fs2 = FxFilesystem::open(snapshot).await.expect("open failed");
4056 assert_eq!(fs2.object_manager().reserved_metadata_space(), expected_map);
4057 fsck(fs2.clone()).await.expect("fsck failed");
4058 fs2.close().await.expect("close failed");
4059 }
4060
4061 #[fuchsia::test]
4062 async fn test_legacy_borrowed_proportional_distribution() {
4063 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
4064 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4065 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4066
4067 let crypt_a = Arc::new(new_insecure_crypt());
4068 let store_a = root_volume
4069 .new_volume(
4070 "store_a",
4071 NewChildStoreOptions {
4072 options: StoreOptions {
4073 crypt: Some(crypt_a.clone()),
4074 ..StoreOptions::default()
4075 },
4076 ..NewChildStoreOptions::default()
4077 },
4078 )
4079 .await
4080 .expect("new_volume failed");
4081 let store_a_id = store_a.store_object_id();
4082 let root_dir_a = store_a.root_directory_object_id();
4083
4084 let store_b = root_volume
4085 .new_volume("store_b", NewChildStoreOptions::default())
4086 .await
4087 .expect("new_volume failed");
4088 let store_b_id = store_b.store_object_id();
4089 let root_dir_b = store_b.root_directory_object_id();
4090
4091 for i in 0..16 {
4093 let mut transaction = store_a
4094 .new_transaction(
4095 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4096 Options::default(),
4097 )
4098 .await
4099 .expect("new_transaction failed");
4100 transaction.add(
4101 store_a_id,
4102 Mutation::replace_or_insert_object(
4103 ObjectKey::extended_attribute(root_dir_a, format!("attr_{i}").into_bytes()),
4104 ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4105 ),
4106 );
4107 transaction.commit().await.expect("commit failed");
4108 }
4109 {
4110 let mut transaction = store_b
4111 .new_transaction(
4112 lock_keys![LockKey::object(store_b_id, root_dir_b)],
4113 Options::default(),
4114 )
4115 .await
4116 .expect("new_transaction failed");
4117 transaction.add(
4118 store_b_id,
4119 Mutation::replace_or_insert_object(
4120 ObjectKey::extended_attribute(root_dir_b, b"attr_0".to_vec()),
4121 ObjectValue::inline_extended_attribute(vec![0xbb; 1024]),
4122 ),
4123 );
4124 transaction.commit().await.expect("commit failed");
4125 }
4126
4127 fs.journal().force_compact().await.expect("force_compact failed");
4128 fs.journal().stop_compactions().await;
4129
4130 {
4133 let mut transaction = store_a
4134 .new_transaction(
4135 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4136 Options::default(),
4137 )
4138 .await
4139 .expect("new_transaction failed");
4140 transaction.add(
4141 store_a_id,
4142 Mutation::replace_or_insert_object(
4143 ObjectKey::extended_attribute(root_dir_a, b"locked_attr".to_vec()),
4144 ObjectValue::inline_extended_attribute(vec![0xcc; 1024]),
4145 ),
4146 );
4147 transaction.commit().await.expect("commit failed");
4148 }
4149 store_a.lock().await.expect("lock failed");
4150 store_a.flush().await.expect("locked flush failed");
4151 fs.journal().write_super_block().await.expect("write_super_block failed");
4152 fs.journal().write_super_block().await.expect("write_super_block failed");
4153
4154 {
4158 let mut transaction = store_b
4159 .new_transaction(
4160 lock_keys![LockKey::object(store_b_id, root_dir_b)],
4161 Options::default(),
4162 )
4163 .await
4164 .expect("new_transaction failed");
4165 transaction.add(
4166 store_b_id,
4167 Mutation::replace_or_insert_object(
4168 ObjectKey::extended_attribute(root_dir_b, b"funded_attr".to_vec()),
4169 ObjectValue::inline_extended_attribute(vec![0xdd; 64]),
4170 ),
4171 );
4172 transaction.commit().await.expect("commit failed");
4173 }
4174 let offset_before_legacy_borrowed = fs.object_manager().last_end_offset();
4175 {
4176 let mut transaction = store_b
4177 .new_transaction(
4178 lock_keys![LockKey::object(store_b_id, root_dir_b)],
4179 Options {
4180 skip_journal_checks: true,
4181 reservation: ReservationOptions::Borrowed,
4182 ..Default::default()
4183 },
4184 )
4185 .await
4186 .expect("new_transaction failed");
4187 transaction.add(
4188 store_b_id,
4189 Mutation::replace_or_insert_object(
4190 ObjectKey::extended_attribute(root_dir_b, b"legacy_borrowed_attr".to_vec()),
4191 ObjectValue::None,
4192 ),
4193 );
4194 transaction.commit().await.expect("commit failed");
4195 }
4196 let offset_after_legacy_borrowed = fs.object_manager().last_end_offset();
4197
4198 let (_, req_a) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4199 let (_, req_b) = fs.object_manager().store_borrowed(store_b_id).unwrap();
4200 assert!(req_a > req_b);
4201
4202 let (_, min_checkpoint) = fs.object_manager().journal_file_offsets();
4205 let min_offset = min_checkpoint.unwrap().file_offset;
4206 let journal_usage = offset_after_legacy_borrowed - min_offset;
4207 let info_a = store_a.load_store_info().await.unwrap();
4208 let mut store_a_layer_size = 0;
4209 for &oid in &info_a.layers {
4210 store_a_layer_size += fs.root_store().get_file_size(oid).await.unwrap();
4211 }
4212 assert_ne!(info_a.encrypted_mutations_object_id, INVALID_OBJECT_ID);
4213 store_a_layer_size +=
4214 fs.root_store().get_file_size(info_a.encrypted_mutations_object_id).await.unwrap() * 3;
4215 let max_layer_size = [
4216 fs.allocator()
4217 .tree()
4218 .immutable_layer_set()
4219 .layers
4220 .iter()
4221 .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4222 .sum::<u64>(),
4223 fs.root_store()
4224 .tree()
4225 .immutable_layer_set()
4226 .layers
4227 .iter()
4228 .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4229 .sum::<u64>(),
4230 store_a_layer_size,
4231 store_b
4232 .tree()
4233 .immutable_layer_set()
4234 .layers
4235 .iter()
4236 .map(|l| l.handle().map(ReadObjectHandle::get_size).unwrap_or(0))
4237 .sum::<u64>(),
4238 ]
4239 .into_iter()
4240 .max()
4241 .unwrap();
4242 assert_eq!(max_layer_size, store_a_layer_size);
4243 let legacy_required = max_layer_size * 3 / 2 + journal_usage * 4 + 1_048_576;
4244 let new_required = fs.object_manager().required_reservation();
4245 assert!(new_required > legacy_required);
4246
4247 let initial_legacy_borrowed = 1_048_576 / 4;
4248 let expected_final_legacy_borrowed = initial_legacy_borrowed
4249 + (offset_after_legacy_borrowed - offset_before_legacy_borrowed) * 4;
4250
4251 fs.sync(SyncOptions { flush_device: true, ..Default::default() })
4252 .await
4253 .expect("sync failed");
4254 let snapshot_legacy = fs.device().snapshot().expect("snapshot failed");
4255 fs.close().await.expect("close failed");
4256
4257 let fs2 = FxFilesystemBuilder::new()
4262 .on_new_store(move |store| {
4263 if store.store_object_id() == store_a_id {
4264 store
4265 .filesystem()
4266 .ensure_local()
4267 .object_manager()
4268 .init_replay_metadata_reservation(
4269 Version { major: 59, minor: 0 },
4270 &HashMap::from_iter([(0, expected_final_legacy_borrowed)]),
4271 );
4272 }
4273 })
4274 .open(snapshot_legacy)
4275 .await
4276 .expect("open failed");
4277
4278 let expected_reservation = legacy_required - expected_final_legacy_borrowed;
4279 assert_eq!(fs2.object_manager().metadata_reservation().amount(), expected_reservation);
4280 assert_eq!(
4281 fs2.object_manager().borrowed_metadata_space(),
4282 new_required - expected_reservation
4283 );
4284 let (b_a, r_a) = fs2.object_manager().store_borrowed(store_a_id).unwrap();
4285 let (b_b, r_b) = fs2.object_manager().store_borrowed(store_b_id).unwrap();
4286 assert!(b_a > 0 && b_a < r_a);
4287 assert!(b_b > 0 && b_b < r_b);
4288 fsck(fs2.clone()).await.expect("fsck failed");
4289 fs2.close().await.expect("close failed");
4290 }
4291
4292 #[fuchsia::test]
4293 async fn test_edge_cases_shrink_minor_layer_both_checkpoints_and_system_compaction() {
4294 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
4295 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4296 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4297
4298 let store_a = root_volume
4299 .new_volume("store_a", NewChildStoreOptions::default())
4300 .await
4301 .expect("new_volume failed");
4302 let store_a_id = store_a.store_object_id();
4303 let root_dir_a = store_a.root_directory_object_id();
4304
4305 for batch in 0..28 {
4307 let mut transaction = store_a
4308 .new_transaction(
4309 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4310 Options::default(),
4311 )
4312 .await
4313 .expect("new_transaction failed");
4314 for i in 0..50 {
4315 let idx = batch * 50 + i;
4316 transaction.add(
4317 store_a_id,
4318 Mutation::replace_or_insert_object(
4319 ObjectKey::extended_attribute(
4320 root_dir_a,
4321 format!("base_{idx:04}_{:<200}", idx).into_bytes(),
4322 ),
4323 ObjectValue::inline_extended_attribute(vec![0x55; 200]),
4324 ),
4325 );
4326 }
4327 transaction.commit().await.expect("commit failed");
4328 }
4329 store_a
4330 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4331 .await
4332 .expect("major flush failed");
4333 fs.journal().force_compact().await.expect("force_compact failed");
4334 fs.journal().stop_compactions().await;
4335 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4336
4337 {
4341 let mut transaction = store_a
4342 .new_transaction(
4343 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4344 Options {
4345 skip_journal_checks: true,
4346 reservation: ReservationOptions::Borrowed,
4347 ..Default::default()
4348 },
4349 )
4350 .await
4351 .expect("new_transaction failed");
4352 for i in 0..20 {
4353 transaction.add(
4354 store_a_id,
4355 Mutation::replace_or_insert_object(
4356 ObjectKey::extended_attribute(
4357 root_dir_a,
4358 format!("minor_attr_{i:02}").into_bytes(),
4359 ),
4360 ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4361 ),
4362 );
4363 }
4364 transaction.commit().await.expect("commit failed");
4365
4366 let mut begin_txn = fs
4367 .root_store()
4368 .new_transaction(
4369 lock_keys![],
4370 Options {
4371 skip_journal_checks: true,
4372 reservation: ReservationOptions::Borrowed,
4373 ..Default::default()
4374 },
4375 )
4376 .await
4377 .expect("new_transaction failed");
4378 begin_txn.add(store_a_id, Mutation::BeginFlush);
4379 begin_txn.commit().await.expect("commit failed");
4380
4381 let mut transaction = store_a
4382 .new_transaction(
4383 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4384 Options {
4385 skip_journal_checks: true,
4386 reservation: ReservationOptions::Borrowed,
4387 ..Default::default()
4388 },
4389 )
4390 .await
4391 .expect("new_transaction failed");
4392 for i in 20..40 {
4393 transaction.add(
4394 store_a_id,
4395 Mutation::replace_or_insert_object(
4396 ObjectKey::extended_attribute(
4397 root_dir_a,
4398 format!("minor_attr_{i:02}").into_bytes(),
4399 ),
4400 ObjectValue::inline_extended_attribute(vec![0xaa; 1024]),
4401 ),
4402 );
4403 }
4404 transaction.commit().await.expect("commit failed");
4405 }
4406 store_a
4407 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4408 .await
4409 .expect("minor flush failed");
4410 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4411 let (layer_borrowed_after_grow, _) =
4412 fs.object_manager().store_borrowed(store_a_id).unwrap();
4413 assert!(layer_borrowed_after_grow > 0);
4414
4415 {
4420 let mut transaction = store_a
4421 .new_transaction(
4422 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4423 Options {
4424 skip_journal_checks: true,
4425 reservation: ReservationOptions::Borrowed,
4426 ..Default::default()
4427 },
4428 )
4429 .await
4430 .expect("new_transaction failed");
4431 transaction.add(
4432 store_a_id,
4433 Mutation::replace_or_insert_object(
4434 ObjectKey::extended_attribute(root_dir_a, b"unflushed_before_sb".to_vec()),
4435 ObjectValue::None,
4436 ),
4437 );
4438 transaction.commit().await.expect("commit failed");
4439 }
4440 let (total_b_with_unflushed, _) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4441 assert!(total_b_with_unflushed > layer_borrowed_after_grow);
4442 fs.journal().write_super_block().await.expect("write_super_block failed");
4443 {
4444 let snapshot = fs.device().snapshot().expect("snapshot failed");
4445 let fs_replay = FxFilesystem::open(snapshot).await.expect("open failed");
4446 assert_eq!(
4447 fs_replay.object_manager().store_borrowed(store_a_id).unwrap().0,
4448 total_b_with_unflushed
4449 );
4450 fs_replay.close().await.expect("close failed");
4451 }
4452
4453 {
4457 let mut transaction = store_a
4458 .new_transaction(
4459 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4460 Options::default(),
4461 )
4462 .await
4463 .expect("new_transaction failed");
4464 for i in 0..40 {
4465 transaction.add(
4466 store_a_id,
4467 Mutation::replace_or_insert_object(
4468 ObjectKey::extended_attribute(
4469 root_dir_a,
4470 format!("minor_attr_{i:02}").into_bytes(),
4471 ),
4472 ObjectValue::None,
4473 ),
4474 );
4475 }
4476 transaction.commit().await.expect("commit failed");
4477 }
4478 let res_before_shrink = fs.object_manager().reservation(store_a_id).unwrap();
4479 store_a
4480 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4481 .await
4482 .expect("minor flush failed");
4483 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4484 let res_after_shrink = fs.object_manager().reservation(store_a_id).unwrap();
4485 assert!(res_after_shrink < res_before_shrink);
4486 let (layer_borrowed_after_shrink, _) =
4487 fs.object_manager().store_borrowed(store_a_id).unwrap();
4488 assert_eq!(
4489 layer_borrowed_after_shrink,
4490 layer_borrowed_after_grow.saturating_sub(res_before_shrink - res_after_shrink)
4491 );
4492
4493 fs.object_manager().set_store_borrowed(store_a_id, 0);
4499 {
4500 let root_store = fs.root_store();
4501 let root_id = root_store.store_object_id();
4502 let graveyard_id = root_store.graveyard_directory_object_id();
4503 let mut transaction = root_store
4504 .new_transaction(
4505 lock_keys![LockKey::object(root_id, graveyard_id)],
4506 Options::default(),
4507 )
4508 .await
4509 .expect("new_transaction failed");
4510 transaction.add(
4511 root_id,
4512 Mutation::replace_or_insert_object(
4513 ObjectKey::extended_attribute(graveyard_id, b"sys_test".to_vec()),
4514 ObjectValue::None,
4515 ),
4516 );
4517 transaction.commit().await.expect("commit failed");
4518 }
4519 let current_res = fs.object_manager().metadata_reservation().amount();
4520 fs.object_manager().metadata_reservation().give_back(current_res / 2);
4521 fs.object_manager()
4522 .flush(FlushReason::Journal(ForceMajor::False))
4523 .await
4524 .expect("flush failed");
4525 assert_eq!(
4526 store_a.load_store_info().await.unwrap().layers.len(),
4527 2,
4528 "System borrowing must not force major compaction on child store_a when B_A == 0"
4529 );
4530 let restored = fs.allocator().reserve(None, current_res / 2).unwrap();
4531 restored.move_to(fs.object_manager().metadata_reservation(), current_res / 2);
4532 fsck(fs.clone()).await.expect("fsck failed");
4533 fs.close().await.expect("close failed");
4534 }
4535
4536 #[fuchsia::test]
4537 async fn test_locked_store_isolation_and_minor_flush_reservation() {
4538 let device = DeviceHolder::new(FakeDevice::new(16384, 4096));
4539 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4540 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
4541
4542 let crypt_a = Arc::new(new_insecure_crypt());
4543 let store_a = root_volume
4544 .new_volume(
4545 "store_a",
4546 NewChildStoreOptions {
4547 options: StoreOptions {
4548 crypt: Some(crypt_a.clone()),
4549 ..StoreOptions::default()
4550 },
4551 ..NewChildStoreOptions::default()
4552 },
4553 )
4554 .await
4555 .expect("new_volume failed");
4556 let store_a_id = store_a.store_object_id();
4557 let root_dir_a = store_a.root_directory_object_id();
4558
4559 let crypt_b = Arc::new(new_insecure_crypt());
4560 let store_b = root_volume
4561 .new_volume(
4562 "store_b",
4563 NewChildStoreOptions {
4564 options: StoreOptions {
4565 crypt: Some(crypt_b.clone()),
4566 ..StoreOptions::default()
4567 },
4568 ..NewChildStoreOptions::default()
4569 },
4570 )
4571 .await
4572 .expect("new_volume failed");
4573 let store_b_id = store_b.store_object_id();
4574 let root_dir_b = store_b.root_directory_object_id();
4575
4576 for (store, store_id, dir_id) in
4578 [(&store_a, store_a_id, root_dir_a), (&store_b, store_b_id, root_dir_b)]
4579 {
4580 for batch in 0..28 {
4581 let mut transaction = store
4582 .new_transaction(
4583 lock_keys![LockKey::object(store_id, dir_id)],
4584 Options::default(),
4585 )
4586 .await
4587 .expect("new_transaction failed");
4588 for i in 0..50 {
4589 let idx = batch * 50 + i;
4590 transaction.add(
4591 store_id,
4592 Mutation::replace_or_insert_object(
4593 ObjectKey::extended_attribute(
4594 dir_id,
4595 format!("attr_{idx:04}_{:<200}", idx).into_bytes(),
4596 ),
4597 ObjectValue::inline_extended_attribute(vec![0x55; 200]),
4598 ),
4599 );
4600 }
4601 transaction.commit().await.expect("commit failed");
4602 }
4603 }
4604
4605 store_a
4606 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4607 .await
4608 .expect("flush failed");
4609 store_b
4610 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4611 .await
4612 .expect("flush failed");
4613 fs.journal().force_compact().await.expect("force_compact failed");
4614 fs.journal().stop_compactions().await;
4615
4616 {
4618 let mut transaction = store_b
4619 .new_transaction(
4620 lock_keys![LockKey::object(store_b_id, root_dir_b)],
4621 Options::default(),
4622 )
4623 .await
4624 .expect("new_transaction failed");
4625 transaction.add(
4626 store_b_id,
4627 Mutation::replace_or_insert_object(
4628 ObjectKey::extended_attribute(root_dir_b, b"extra_1".to_vec()),
4629 ObjectValue::inline_extended_attribute(vec![0x77; 200]),
4630 ),
4631 );
4632 transaction.commit().await.expect("commit failed");
4633 store_b
4634 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4635 .await
4636 .expect("minor flush failed");
4637 }
4638 fs.journal().force_compact().await.expect("force_compact failed");
4639 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4640 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
4641 assert_eq!(fs.object_manager().borrowed_metadata_space(), 0);
4642
4643 {
4649 let mut transaction = store_a
4650 .new_transaction(
4651 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4652 Options {
4653 skip_journal_checks: true,
4654 reservation: ReservationOptions::Borrowed,
4655 ..Default::default()
4656 },
4657 )
4658 .await
4659 .expect("new_transaction failed");
4660 for i in 0..50 {
4661 transaction.add(
4662 store_a_id,
4663 Mutation::replace_or_insert_object(
4664 ObjectKey::extended_attribute(
4665 root_dir_a,
4666 format!("temp_{i:04}_{:<200}", i).into_bytes(),
4667 ),
4668 ObjectValue::None,
4669 ),
4670 );
4671 }
4672 transaction.commit().await.expect("commit failed");
4673 store_a
4674 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4675 .await
4676 .expect("minor flush failed");
4677 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4678 }
4679 fs.journal().force_compact().await.expect("force_compact failed");
4680 store_a
4681 .flush_with_reason(FlushReason::Journal(ForceMajor::True))
4682 .await
4683 .expect("major flush failed");
4684 fs.journal().force_compact().await.expect("force_compact failed");
4685 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4686 assert_eq!(fs.object_manager().store_borrowed(store_a_id).unwrap().0, 0);
4687 assert_eq!(
4688 fs.object_manager().borrowed_metadata_space(),
4689 0,
4690 "reconcile_metadata_reservation must not give away M_total during a minor flush"
4691 );
4692
4693 {
4698 let mut transaction = store_a
4699 .new_transaction(
4700 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4701 Options {
4702 skip_journal_checks: true,
4703 reservation: ReservationOptions::Borrowed,
4704 ..Default::default()
4705 },
4706 )
4707 .await
4708 .expect("new_transaction failed");
4709 for i in 0..50 {
4710 transaction.add(
4711 store_a_id,
4712 Mutation::replace_or_insert_object(
4713 ObjectKey::extended_attribute(
4714 root_dir_a,
4715 format!("temp_{i:04}_{:<200}", i).into_bytes(),
4716 ),
4717 ObjectValue::None,
4718 ),
4719 );
4720 }
4721 transaction.commit().await.expect("commit failed");
4722 store_a
4723 .flush_with_reason(FlushReason::Journal(ForceMajor::False))
4724 .await
4725 .expect("minor flush failed");
4726 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 2);
4727
4728 let mut transaction = store_a
4729 .new_transaction(
4730 lock_keys![LockKey::object(store_a_id, root_dir_a)],
4731 Options {
4732 skip_journal_checks: true,
4733 reservation: ReservationOptions::Borrowed,
4734 ..Default::default()
4735 },
4736 )
4737 .await
4738 .expect("new_transaction failed");
4739 for i in 50..100 {
4740 transaction.add(
4741 store_a_id,
4742 Mutation::replace_or_insert_object(
4743 ObjectKey::extended_attribute(
4744 root_dir_a,
4745 format!("temp_{i:04}_{:<200}", i).into_bytes(),
4746 ),
4747 ObjectValue::None,
4748 ),
4749 );
4750 }
4751 transaction.commit().await.expect("commit failed");
4752 }
4753
4754 store_a.lock().await.expect("lock failed");
4755 fs.journal().force_compact().await.expect("force_compact failed");
4756
4757 let (b_a_locked, req_a_locked) = fs.object_manager().store_borrowed(store_a_id).unwrap();
4758 assert!(b_a_locked > 0);
4759 fs.object_manager().set_store_borrowed(store_a_id, req_a_locked);
4761
4762 fs.object_manager()
4765 .flush(FlushReason::Journal(ForceMajor::False))
4766 .await
4767 .expect("flush failed");
4768 assert_eq!(
4769 store_b.load_store_info().await.unwrap().layers.len(),
4770 2,
4771 "Locked store_a must not force major compactions on unlocked store_b"
4772 );
4773 assert_eq!(fs.object_manager().store_borrowed(store_b_id).unwrap().0, 0);
4774
4775 store_a.unlock(crypt_a).await.expect("unlock failed");
4779 fs.object_manager()
4780 .flush(FlushReason::Journal(ForceMajor::False))
4781 .await
4782 .expect("flush failed");
4783 assert_eq!(store_a.load_store_info().await.unwrap().layers.len(), 1);
4784 assert_eq!(fs.object_manager().store_borrowed(store_a_id).unwrap().0, 0);
4785 assert_eq!(store_b.load_store_info().await.unwrap().layers.len(), 2);
4786
4787 fs.journal().force_compact().await.expect("force_compact failed");
4791 assert_eq!(fs.object_manager().borrowed_metadata_space(), 0);
4792
4793 fsck(fs.clone()).await.expect("fsck failed");
4794 fs.close().await.expect("close failed");
4795 }
4796}
4797
4798#[cfg(fuzz)]
4799mod fuzz {
4800 use fuzz::fuzz;
4801
4802 #[fuzz]
4803 fn fuzz_journal_bytes(input: Vec<u8>) {
4804 use crate::filesystem::FxFilesystem;
4805 use fuchsia_async as fasync;
4806 use std::io::Write;
4807 use storage_device::DeviceHolder;
4808 use storage_device::fake_device::FakeDevice;
4809
4810 fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
4811 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
4812 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4813 fs.journal().inner.lock().writer.write_all(&input).expect("write failed");
4814 fs.close().await.expect("close failed");
4815 let device = fs.take_device().await;
4816 device.reopen(false);
4817 if let Ok(fs) = FxFilesystem::open(device).await {
4818 let _ = fs.close().await;
4821 }
4822 });
4823 }
4824
4825 #[fuzz]
4826 fn fuzz_journal(input: Vec<super::JournalRecord>) {
4827 use crate::filesystem::FxFilesystem;
4828 use fuchsia_async as fasync;
4829 use storage_device::DeviceHolder;
4830 use storage_device::fake_device::FakeDevice;
4831
4832 fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
4833 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
4834 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
4835 {
4836 let mut inner = fs.journal().inner.lock();
4837 for record in &input {
4838 let _ = inner.writer.write_record(record);
4839 }
4840 }
4841 fs.close().await.expect("close failed");
4842 let device = fs.take_device().await;
4843 device.reopen(false);
4844 if let Ok(fs) = FxFilesystem::open(device).await {
4845 let _ = fs.close().await;
4848 }
4849 });
4850 }
4851}