1mod bootstrap_handle;
20mod checksum_list;
21mod reader;
22pub mod super_block;
23mod writer;
24
25use crate::checksum::{Checksum, Checksums, ChecksumsV38};
26use crate::errors::FxfsError;
27use crate::filesystem::{ApplyContext, ApplyMode, FxFilesystem, SyncOptions};
28use crate::log::*;
29use crate::lsm_tree::cache::NullCache;
30use crate::lsm_tree::types::Layer;
31use crate::object_handle::{ObjectHandle as _, ReadObjectHandle};
32use crate::object_store::allocator::Allocator;
33use crate::object_store::data_object_handle::OverwriteOptions;
34use crate::object_store::extent_record::{ExtentMode, ExtentValue};
35use crate::object_store::graveyard::Graveyard;
36use crate::object_store::journal::bootstrap_handle::BootstrapObjectHandle;
37use crate::object_store::journal::checksum_list::ChecksumList;
38use crate::object_store::journal::reader::{JournalReader, ReadResult};
39use crate::object_store::journal::super_block::{
40 SuperBlockHeader, SuperBlockInstance, SuperBlockManager,
41};
42use crate::object_store::journal::writer::JournalWriter;
43use crate::object_store::object_manager::ObjectManager;
44use crate::object_store::object_record::{AttributeKey, ObjectKey, ObjectKeyData, ObjectValue};
45use crate::object_store::transaction::{
46 AllocatorMutation, LockKey, Mutation, MutationV40, MutationV41, MutationV43, MutationV46,
47 MutationV47, MutationV49, MutationV50, MutationV54, MutationV55, MutationV56,
48 ObjectStoreMutation, Options, TRANSACTION_MAX_JOURNAL_USAGE, Transaction, TxnMutation,
49 lock_keys,
50};
51use crate::object_store::{
52 AssocObj, AttributeId, DataObjectHandle, Extent, HandleOptions, HandleOwner, INVALID_OBJECT_ID,
53 Item, ItemRef, NewChildStoreOptions, ObjectStore, ReservedId,
54};
55use crate::range::RangeExt;
56use crate::round::{round_div, round_down};
57use crate::serialized_types::{
58 LATEST_VERSION, Migrate, Version, Versioned, migrate_nodefault, migrate_to_version,
59};
60use anyhow::{Context, Error, anyhow, bail, ensure};
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 static_assertions::const_assert;
70use std::clone::Clone;
71use std::collections::HashSet;
72use std::num::NonZero;
73use std::ops::{Bound, Range};
74use std::sync::atomic::{AtomicBool, Ordering};
75use std::sync::{Arc, OnceLock};
76use std::task::{Poll, Waker};
77use storage_device::Device;
78
79pub const BLOCK_SIZE: u64 = 4096;
81
82const CHUNK_SIZE: u64 = 131_072;
84const_assert!(CHUNK_SIZE > TRANSACTION_MAX_JOURNAL_USAGE);
85
86pub const DEFAULT_RECLAIM_SIZE: u64 = 524_288;
88
89pub const RESERVED_SPACE: u64 = 1_048_576;
92
93const RESET_XOR: u64 = 0xffffffffffffffff;
98
99pub type JournalCheckpoint = JournalCheckpointV32;
103
104#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TypeFingerprint)]
105pub struct JournalCheckpointV32 {
106 pub file_offset: u64,
107
108 pub checksum: Checksum,
111
112 pub version: Version,
116}
117
118pub type JournalRecord = JournalRecordV56;
119
120#[allow(clippy::large_enum_variant)]
121#[derive(Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
122#[cfg_attr(fuzz, derive(arbitrary::Arbitrary))]
123pub enum JournalRecordV56 {
124 EndBlock,
125 Mutation {
126 object_id: u64,
127 mutation: MutationV56,
128 },
129 Commit,
131 Discard(u64),
133 DidFlushDevice(u64),
143 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
151}
152
153#[allow(clippy::large_enum_variant)]
154#[derive(Migrate, Clone, Debug, Serialize, Deserialize, TypeFingerprint, Versioned)]
155#[migrate_to_version(JournalRecordV56)]
156pub enum JournalRecordV55 {
157 EndBlock,
158 Mutation { object_id: u64, mutation: MutationV55 },
159 Commit,
160 Discard(u64),
161 DidFlushDevice(u64),
162 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
163}
164
165#[allow(clippy::large_enum_variant)]
166#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
167#[migrate_to_version(JournalRecordV55)]
168#[migrate_nodefault]
169pub enum JournalRecordV54 {
170 EndBlock,
171 Mutation { object_id: u64, mutation: MutationV54 },
172 Commit,
173 Discard(u64),
174 DidFlushDevice(u64),
175 DataChecksums(Range<u64>, crate::checksum::ChecksumsV38, bool),
176}
177
178#[allow(clippy::large_enum_variant)]
179#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
180#[migrate_to_version(JournalRecordV54)]
181#[migrate_nodefault]
182pub enum JournalRecordV50 {
183 EndBlock,
184 Mutation { object_id: u64, mutation: MutationV50 },
185 Commit,
186 Discard(u64),
187 DidFlushDevice(u64),
188 DataChecksums(Range<u64>, ChecksumsV38, bool),
189}
190
191#[allow(clippy::large_enum_variant)]
192#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
193#[migrate_to_version(JournalRecordV50)]
194pub enum JournalRecordV49 {
195 EndBlock,
196 Mutation { object_id: u64, mutation: MutationV49 },
197 Commit,
198 Discard(u64),
199 DidFlushDevice(u64),
200 DataChecksums(Range<u64>, ChecksumsV38, bool),
201}
202
203#[allow(clippy::large_enum_variant)]
204#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
205#[migrate_to_version(JournalRecordV49)]
206pub enum JournalRecordV47 {
207 EndBlock,
208 Mutation { object_id: u64, mutation: MutationV47 },
209 Commit,
210 Discard(u64),
211 DidFlushDevice(u64),
212 DataChecksums(Range<u64>, ChecksumsV38, bool),
213}
214
215#[allow(clippy::large_enum_variant)]
216#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
217#[migrate_to_version(JournalRecordV47)]
218pub enum JournalRecordV46 {
219 EndBlock,
220 Mutation { object_id: u64, mutation: MutationV46 },
221 Commit,
222 Discard(u64),
223 DidFlushDevice(u64),
224 DataChecksums(Range<u64>, ChecksumsV38, bool),
225}
226
227#[allow(clippy::large_enum_variant)]
228#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
229#[migrate_to_version(JournalRecordV46)]
230pub enum JournalRecordV43 {
231 EndBlock,
232 Mutation { object_id: u64, mutation: MutationV43 },
233 Commit,
234 Discard(u64),
235 DidFlushDevice(u64),
236 DataChecksums(Range<u64>, ChecksumsV38, bool),
237}
238
239#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
240#[migrate_to_version(JournalRecordV43)]
241pub enum JournalRecordV42 {
242 EndBlock,
243 Mutation { object_id: u64, mutation: MutationV41 },
244 Commit,
245 Discard(u64),
246 DidFlushDevice(u64),
247 DataChecksums(Range<u64>, ChecksumsV38, bool),
248}
249
250#[derive(Serialize, Deserialize, TypeFingerprint, Versioned)]
251pub enum JournalRecordV41 {
252 EndBlock,
253 Mutation { object_id: u64, mutation: MutationV41 },
254 Commit,
255 Discard(u64),
256 DidFlushDevice(u64),
257 DataChecksums(Range<u64>, ChecksumsV38),
258}
259
260impl From<JournalRecordV41> for JournalRecordV42 {
261 fn from(record: JournalRecordV41) -> Self {
262 match record {
263 JournalRecordV41::EndBlock => Self::EndBlock,
264 JournalRecordV41::Mutation { object_id, mutation } => {
265 Self::Mutation { object_id, mutation: mutation.into() }
266 }
267 JournalRecordV41::Commit => Self::Commit,
268 JournalRecordV41::Discard(offset) => Self::Discard(offset),
269 JournalRecordV41::DidFlushDevice(offset) => Self::DidFlushDevice(offset),
270 JournalRecordV41::DataChecksums(range, sums) => {
271 Self::DataChecksums(range, sums, true)
274 }
275 }
276 }
277}
278
279#[derive(Migrate, Serialize, Deserialize, TypeFingerprint, Versioned)]
280#[migrate_to_version(JournalRecordV41)]
281pub enum JournalRecordV40 {
282 EndBlock,
283 Mutation { object_id: u64, mutation: MutationV40 },
284 Commit,
285 Discard(u64),
286 DidFlushDevice(u64),
287 DataChecksums(Range<u64>, ChecksumsV38),
288}
289
290pub(super) fn journal_handle_options() -> HandleOptions {
291 HandleOptions { skip_journal_checks: true, ..Default::default() }
292}
293
294pub struct Journal {
300 objects: Arc<ObjectManager>,
301 handle: OnceLock<DataObjectHandle<ObjectStore>>,
302 super_block_manager: SuperBlockManager,
303 inner: Mutex<Inner>,
304 writer_mutex: Mutex<()>,
305 sync_mutex: futures::lock::Mutex<()>,
306 trace: AtomicBool,
307
308 reclaim_event: Event,
310}
311
312struct Inner {
313 super_block_header: SuperBlockHeader,
314
315 zero_offset: Option<u64>,
317
318 device_flushed_offset: u64,
320
321 needs_did_flush_device: bool,
323
324 writer: JournalWriter,
326
327 output_reset_version: bool,
330
331 flush_waker: Option<Waker>,
333
334 terminate: bool,
336
337 terminate_reason: Option<Error>,
339
340 disable_compactions: bool,
342
343 compaction_running: bool,
345
346 sync_waker: Option<Waker>,
348
349 flushed_offset: u64,
351
352 valid_to: u64,
357
358 discard_offset: Option<u64>,
362
363 reclaim_size: u64,
367
368 image_builder_mode: Option<SuperBlockInstance>,
369
370 barriers_enabled: bool,
374
375 needs_barrier: bool,
378
379 forced_compaction: bool,
381}
382
383impl Inner {
384 fn terminate(&mut self, reason: Option<Error>) {
385 self.terminate = true;
386
387 if let Some(err) = reason {
388 error!(error:? = err; "Terminating journal");
389 if let Some(prev_err) = self.terminate_reason.as_ref() {
391 error!(error:? = prev_err; "Journal previously terminated");
392 } else {
393 self.terminate_reason = Some(err);
394 }
395 }
396
397 if let Some(waker) = self.flush_waker.take() {
398 waker.wake();
399 }
400 if let Some(waker) = self.sync_waker.take() {
401 waker.wake();
402 }
403 }
404}
405
406pub struct JournalOptions {
407 pub reclaim_size: u64,
411
412 pub barriers_enabled: bool,
416}
417
418impl Default for JournalOptions {
419 fn default() -> Self {
420 JournalOptions { reclaim_size: DEFAULT_RECLAIM_SIZE, barriers_enabled: false }
421 }
422}
423
424struct JournaledTransactions {
425 transactions: Vec<JournaledTransaction>,
426 device_flushed_offset: u64,
427}
428
429#[derive(Debug, Default)]
430pub struct JournaledTransaction {
431 pub checkpoint: JournalCheckpoint,
432 pub root_parent_mutations: Vec<Mutation>,
433 pub root_mutations: Vec<Mutation>,
434 pub non_root_mutations: Vec<(u64, Mutation)>,
436 pub end_offset: u64,
437 pub checksums: Vec<JournaledChecksums>,
438
439 pub end_flush: Option<(u64, u64)>,
442
443 pub volume_deleted: Option<u64>,
445}
446
447impl JournaledTransaction {
448 fn new(checkpoint: JournalCheckpoint) -> Self {
449 Self { checkpoint, ..Default::default() }
450 }
451}
452
453const VOLUME_DELETED: u64 = u64::MAX;
454
455#[derive(Debug)]
456pub struct JournaledChecksums {
457 pub device_range: Range<u64>,
458 pub checksums: Checksums,
459 pub first_write: bool,
460}
461
462pub trait JournalHandle: ReadObjectHandle {
465 fn end_offset(&self) -> Option<u64>;
469 fn push_extent(&mut self, added_offset: u64, device_range: Range<u64>);
473 fn discard_extents(&mut self, discard_offset: u64);
475}
476
477impl<S: HandleOwner> JournalHandle for DataObjectHandle<S> {
481 fn end_offset(&self) -> Option<u64> {
482 None
483 }
484 fn push_extent(&mut self, _added_offset: u64, _device_range: Range<u64>) {
485 }
487 fn discard_extents(&mut self, _discard_offset: u64) {
488 }
490}
491
492#[fxfs_trace::trace]
493impl Journal {
494 pub fn new(objects: Arc<ObjectManager>, options: JournalOptions) -> Journal {
495 let starting_checksum = rand::random_range(1..u64::MAX);
496 Journal {
497 objects: objects,
498 handle: OnceLock::new(),
499 super_block_manager: SuperBlockManager::new(),
500 inner: Mutex::new(Inner {
501 super_block_header: SuperBlockHeader::default(),
502 zero_offset: None,
503 device_flushed_offset: 0,
504 needs_did_flush_device: false,
505 writer: JournalWriter::new(BLOCK_SIZE as usize, starting_checksum),
506 output_reset_version: false,
507 flush_waker: None,
508 terminate: false,
509 terminate_reason: None,
510 disable_compactions: false,
511 compaction_running: false,
512 sync_waker: None,
513 flushed_offset: 0,
514 valid_to: 0,
515 discard_offset: None,
516 reclaim_size: options.reclaim_size,
517 image_builder_mode: None,
518 barriers_enabled: options.barriers_enabled,
519 needs_barrier: false,
520 forced_compaction: false,
521 }),
522 writer_mutex: Mutex::new(()),
523 sync_mutex: futures::lock::Mutex::new(()),
524 trace: AtomicBool::new(false),
525 reclaim_event: Event::new(),
526 }
527 }
528
529 pub fn set_trace(&self, trace: bool) {
530 let old_value = self.trace.swap(trace, Ordering::Relaxed);
531 if trace != old_value {
532 info!(trace; "J: trace");
533 }
534 }
535
536 pub fn set_image_builder_mode(&self, mode: Option<SuperBlockInstance>) {
537 self.inner.lock().image_builder_mode = mode;
538 if let Some(instance) = mode {
539 *self.super_block_manager.next_instance.lock() = instance;
540 }
541 }
542
543 pub fn image_builder_mode(&self) -> Option<SuperBlockInstance> {
544 self.inner.lock().image_builder_mode
545 }
546
547 #[cfg(feature = "migration")]
548 pub fn set_filesystem_uuid(&self, uuid: &[u8; 16]) -> Result<(), Error> {
549 ensure!(
550 self.inner.lock().image_builder_mode.is_some(),
551 "Can only set filesystem uuid in image builder mode."
552 );
553 self.inner.lock().super_block_header.guid.0 = uuid::Uuid::from_bytes(*uuid);
554 Ok(())
555 }
556
557 pub(crate) async fn read_superblocks(
558 &self,
559 device: Arc<dyn Device>,
560 block_size: u64,
561 ) -> Result<(SuperBlockHeader, ObjectStore), Error> {
562 self.super_block_manager.load(device, block_size).await
563 }
564
565 fn validate_mutation(&self, mutation: &Mutation, block_size: u64, device_size: u64) -> bool {
569 match mutation {
570 Mutation::ObjectStore(ObjectStoreMutation {
571 item:
572 Item {
573 key:
574 ObjectKey {
575 data: ObjectKeyData::Attribute(_, AttributeKey::Extent(extent)),
576 ..
577 },
578 value: ObjectValue::Extent(ExtentValue::Some { device_offset, mode, .. }),
579 ..
580 },
581 ..
582 }) => {
583 if extent.is_empty() || !extent.is_aligned(block_size) {
584 return false;
585 }
586 let len = extent.length().unwrap();
587 if let ExtentMode::Cow(checksums) = mode {
588 if checksums.len() > 0 {
589 if len % checksums.len() as u64 != 0 {
590 return false;
591 }
592 if (len / checksums.len() as u64) % block_size != 0 {
593 return false;
594 }
595 }
596 }
597 if *device_offset % block_size != 0
598 || *device_offset >= device_size
599 || device_size - *device_offset < len
600 {
601 return false;
602 }
603 }
604 Mutation::ObjectStore(_) => {}
605 Mutation::EncryptedObjectStore(_) => {}
606 Mutation::Allocator(AllocatorMutation::Allocate { device_range, owner_object_id }) => {
607 return !device_range.is_empty()
608 && *owner_object_id != INVALID_OBJECT_ID
609 && device_range.end <= device_size;
610 }
611 Mutation::Allocator(AllocatorMutation::Deallocate {
612 device_range,
613 owner_object_id,
614 }) => {
615 return !device_range.is_empty()
616 && *owner_object_id != INVALID_OBJECT_ID
617 && device_range.end <= device_size;
618 }
619 Mutation::Allocator(AllocatorMutation::MarkForDeletion(owner_object_id)) => {
620 return *owner_object_id != INVALID_OBJECT_ID;
621 }
622 Mutation::Allocator(AllocatorMutation::SetLimit { owner_object_id, .. }) => {
623 return *owner_object_id != INVALID_OBJECT_ID;
624 }
625 Mutation::BeginFlush => {}
626 Mutation::EndFlush => {}
627 Mutation::DeleteVolume => {}
628 Mutation::UpdateBorrowed(_) => {}
629 Mutation::UpdateMutationsKey(_) => {}
630 Mutation::CreateInternalDir(owner_object_id) => {
631 return *owner_object_id != INVALID_OBJECT_ID;
632 }
633 }
634 true
635 }
636
637 fn update_checksum_list(
639 &self,
640 journal_offset: u64,
641 mutation: &Mutation,
642 checksum_list: &mut ChecksumList,
643 ) -> Result<(), Error> {
644 match mutation {
645 Mutation::ObjectStore(_) => {}
646 Mutation::Allocator(AllocatorMutation::Deallocate { device_range, .. }) => {
647 checksum_list.mark_deallocated(journal_offset, device_range.clone().into());
648 }
649 _ => {}
650 }
651 Ok(())
652 }
653
654 #[trace]
656 pub async fn replay(
657 &self,
658 filesystem: Arc<FxFilesystem>,
659 on_new_allocator: Option<Box<dyn Fn(Arc<Allocator>) + Send + Sync>>,
660 ) -> Result<(), Error> {
661 let block_size = filesystem.block_size();
662
663 let (super_block, root_parent) =
664 self.super_block_manager.load(filesystem.device(), block_size).await?;
665
666 let root_parent = Arc::new(ObjectStore::attach_filesystem(root_parent, filesystem.clone()));
667
668 self.objects.set_root_parent_store(root_parent.clone());
669 let allocator =
670 Arc::new(Allocator::new(filesystem.clone(), super_block.allocator_object_id));
671 if let Some(on_new_allocator) = on_new_allocator {
672 on_new_allocator(allocator.clone());
673 }
674 self.objects.set_allocator(allocator.clone());
675 self.objects.set_borrowed_metadata_space(super_block.borrowed_metadata_space);
676 self.objects.set_last_end_offset(super_block.super_block_journal_file_offset);
677 {
678 let mut inner = self.inner.lock();
679 inner.super_block_header = super_block.clone();
680 }
681
682 let device = filesystem.device();
683
684 let mut handle;
685 {
686 let root_parent_layer = root_parent.tree().mutable_layer();
687 let mut iter = root_parent_layer
688 .seek(Bound::Included(&ObjectKey::attribute(
689 super_block.journal_object_id,
690 AttributeId::DATA,
691 AttributeKey::Extent(Extent::search_key_from_offset(round_down(
692 super_block.journal_checkpoint.file_offset,
693 BLOCK_SIZE,
694 ))),
695 )))
696 .await
697 .context("Failed to seek root parent store")?;
698 let start_offset = if let Some(ItemRef {
699 key:
700 ObjectKey {
701 data:
702 ObjectKeyData::Attribute(AttributeId::DATA, AttributeKey::Extent(extent)),
703 ..
704 },
705 ..
706 }) = iter.get()
707 {
708 extent.start
709 } else {
710 0
711 };
712 handle = BootstrapObjectHandle::new_with_start_offset(
713 super_block.journal_object_id,
714 device.clone(),
715 start_offset,
716 );
717 while let Some(item) = iter.get() {
718 if !match item.into() {
719 Some((
720 object_id,
721 AttributeId::DATA,
722 extent,
723 ExtentValue::Some { device_offset, .. },
724 )) if object_id == super_block.journal_object_id => {
725 if let Some(end_offset) = handle.end_offset() {
726 if extent.start != end_offset {
727 bail!(anyhow!(FxfsError::Inconsistent).context(format!(
728 "Unexpected journal extent {:?}, expected start: {}",
729 item, end_offset
730 )));
731 }
732 }
733 handle.push_extent(
734 0, *device_offset
736 ..*device_offset + extent.length().context("Invalid extent")?,
737 );
738 true
739 }
740 _ => false,
741 } {
742 break;
743 }
744 iter.advance().await.context("Failed to advance root parent store iterator")?;
745 }
746 }
747
748 let mut reader = JournalReader::new(handle, &super_block.journal_checkpoint);
749 let JournaledTransactions { mut transactions, device_flushed_offset } = self
750 .read_transactions(&mut reader, None, INVALID_OBJECT_ID)
751 .await
752 .context("Reading transactions for replay")?;
753
754 let mut checksum_list = ChecksumList::new(device_flushed_offset);
756 let mut valid_to = reader.journal_file_checkpoint().file_offset;
757 let device_size = device.size();
758 'bad_replay: for JournaledTransaction {
759 checkpoint,
760 root_parent_mutations,
761 root_mutations,
762 non_root_mutations,
763 checksums,
764 ..
765 } in &transactions
766 {
767 for JournaledChecksums { device_range, checksums, first_write } in checksums {
768 checksum_list
769 .push(
770 checkpoint.file_offset,
771 device_range.clone(),
772 checksums.maybe_as_ref().context("Malformed checksums")?,
773 *first_write,
774 )
775 .context("Pushing journal checksum records to checksum list")?;
776 }
777 for mutation in root_parent_mutations
778 .iter()
779 .chain(root_mutations)
780 .chain(non_root_mutations.iter().map(|(_, m)| m))
781 {
782 if !self.validate_mutation(mutation, block_size, device_size) {
783 info!(mutation:?; "Stopping replay at bad mutation");
784 valid_to = checkpoint.file_offset;
785 break 'bad_replay;
786 }
787 self.update_checksum_list(checkpoint.file_offset, &mutation, &mut checksum_list)?;
788 }
789 }
790
791 let valid_to = checksum_list
794 .verify(device.as_ref(), valid_to)
795 .await
796 .context("Failed to validate checksums")?;
797
798 let mut last_checkpoint = reader.journal_file_checkpoint();
801 let mut journal_offsets = super_block.journal_file_offsets.clone();
802
803 for (
806 index,
807 JournaledTransaction {
808 checkpoint,
809 root_parent_mutations,
810 end_flush,
811 volume_deleted,
812 ..
813 },
814 ) in transactions.iter_mut().enumerate()
815 {
816 if checkpoint.file_offset >= valid_to {
817 last_checkpoint = checkpoint.clone();
818
819 transactions.truncate(index);
821 break;
822 }
823
824 let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
825 for mutation in root_parent_mutations.drain(..) {
826 self.objects
827 .apply_mutation(
828 super_block.root_parent_store_object_id,
829 mutation,
830 &context,
831 AssocObj::None,
832 )
833 .context("Failed to replay root parent store mutations")?;
834 }
835
836 if let Some((object_id, journal_offset)) = end_flush {
837 journal_offsets.insert(*object_id, *journal_offset);
838 }
839
840 if let Some(object_id) = volume_deleted {
841 journal_offsets.insert(*object_id, VOLUME_DELETED);
842 }
843 }
844
845 let root_store = ObjectStore::open(
847 &root_parent,
848 super_block.root_store_object_id,
849 Box::new(NullCache {}),
850 )
851 .await
852 .context("Unable to open root store")?;
853
854 ensure!(
855 !root_store.is_encrypted(),
856 anyhow!(FxfsError::Inconsistent).context("Root store is encrypted")
857 );
858 self.objects.set_root_store(root_store);
859
860 let root_store_offset =
861 journal_offsets.get(&super_block.root_store_object_id).copied().unwrap_or(0);
862
863 for JournaledTransaction { checkpoint, root_mutations, .. } in &mut transactions {
865 if checkpoint.file_offset < root_store_offset {
866 continue;
867 }
868
869 let context = ApplyContext { mode: ApplyMode::Replay, checkpoint: checkpoint.clone() };
870 for mutation in root_mutations.drain(..) {
871 self.objects
872 .apply_mutation(
873 super_block.root_store_object_id,
874 mutation,
875 &context,
876 AssocObj::None,
877 )
878 .context("Failed to replay root store mutations")?;
879 }
880 }
881
882 allocator.open().await.context("Failed to open allocator")?;
884
885 for JournaledTransaction { checkpoint, non_root_mutations, end_offset, .. } in transactions
887 {
888 self.objects
889 .replay_mutations(
890 non_root_mutations,
891 &journal_offsets,
892 &ApplyContext { mode: ApplyMode::Replay, checkpoint },
893 end_offset,
894 )
895 .await
896 .context("Failed to replay mutations")?;
897 }
898
899 allocator.on_replay_complete().await.context("Failed to complete replay for allocator")?;
900
901 let discarded_to =
902 if last_checkpoint.file_offset != reader.journal_file_checkpoint().file_offset {
903 Some(reader.journal_file_checkpoint().file_offset)
904 } else {
905 None
906 };
907
908 {
910 if last_checkpoint.file_offset < super_block.super_block_journal_file_offset {
911 return Err(anyhow!(FxfsError::Inconsistent).context(format!(
912 "journal replay cut short; journal finishes at {}, but super-block was \
913 written at {}",
914 last_checkpoint.file_offset, super_block.super_block_journal_file_offset
915 )));
916 }
917 let handle = ObjectStore::open_object(
918 &root_parent,
919 super_block.journal_object_id,
920 journal_handle_options(),
921 None,
922 )
923 .await
924 .with_context(|| {
925 format!(
926 "Failed to open journal file (object id: {})",
927 super_block.journal_object_id
928 )
929 })?;
930 let _ = self.handle.set(handle);
931 let mut inner = self.inner.lock();
932 reader.skip_to_end_of_block();
933 let mut writer_checkpoint = reader.journal_file_checkpoint();
934
935 std::mem::drop(reader);
937
938 writer_checkpoint.checksum ^= RESET_XOR;
940 writer_checkpoint.version = LATEST_VERSION;
941 inner.flushed_offset = writer_checkpoint.file_offset;
942
943 inner.device_flushed_offset = inner.flushed_offset;
945
946 inner.writer.seek(writer_checkpoint);
947 inner.output_reset_version = true;
948 inner.valid_to = last_checkpoint.file_offset;
949 if last_checkpoint.file_offset < inner.flushed_offset {
950 inner.discard_offset = Some(last_checkpoint.file_offset);
951 }
952 }
953
954 self.objects
955 .on_replay_complete()
956 .await
957 .context("Failed to complete replay for object manager")?;
958
959 info!(checkpoint = last_checkpoint.file_offset, discarded_to; "replay complete");
960 Ok(())
961 }
962
963 async fn read_transactions(
964 &self,
965 reader: &mut JournalReader,
966 end_offset: Option<u64>,
967 object_id_filter: u64,
968 ) -> Result<JournaledTransactions, Error> {
969 let mut transactions = Vec::new();
970 let (mut device_flushed_offset, root_parent_store_object_id, root_store_object_id) = {
971 let super_block = &self.inner.lock().super_block_header;
972 (
973 super_block.super_block_journal_file_offset,
974 super_block.root_parent_store_object_id,
975 super_block.root_store_object_id,
976 )
977 };
978 let mut current_transaction = None;
979 let mut begin_flush_offsets = HashMap::default();
980 let mut stores_deleted = HashSet::new();
981 loop {
982 let checkpoint = reader.journal_file_checkpoint();
984 if let Some(end_offset) = end_offset {
985 if checkpoint.file_offset >= end_offset {
986 break;
987 }
988 }
989 let result =
990 reader.deserialize().await.context("Failed to deserialize journal record")?;
991 match result {
992 ReadResult::Reset(_) => {
993 if current_transaction.is_some() {
994 current_transaction = None;
995 transactions.pop();
996 }
997 let offset = reader.journal_file_checkpoint().file_offset;
998 if offset > device_flushed_offset {
999 device_flushed_offset = offset;
1000 }
1001 }
1002 ReadResult::Some(record) => {
1003 match record {
1004 JournalRecord::EndBlock => {
1005 reader.skip_to_end_of_block();
1006 }
1007 JournalRecord::Mutation { object_id, mutation } => {
1008 let current_transaction = match current_transaction.as_mut() {
1009 None => {
1010 transactions.push(JournaledTransaction::new(checkpoint));
1011 current_transaction = transactions.last_mut();
1012 current_transaction.as_mut().unwrap()
1013 }
1014 Some(transaction) => transaction,
1015 };
1016
1017 if stores_deleted.contains(&object_id) {
1018 bail!(
1019 anyhow!(FxfsError::Inconsistent)
1020 .context("Encountered mutations for deleted store")
1021 );
1022 }
1023
1024 match &mutation {
1025 Mutation::BeginFlush => {
1026 begin_flush_offsets.insert(
1027 object_id,
1028 current_transaction.checkpoint.file_offset,
1029 );
1030 }
1031 Mutation::EndFlush => {
1032 if let Some(offset) = begin_flush_offsets.remove(&object_id) {
1033 if let Some(deleted_volume) =
1034 ¤t_transaction.volume_deleted
1035 {
1036 if *deleted_volume == object_id {
1037 bail!(anyhow!(FxfsError::Inconsistent).context(
1038 "Multiple EndFlush/DeleteVolume mutations in a \
1039 single transaction for the same object"
1040 ));
1041 }
1042 }
1043 if current_transaction
1047 .end_flush
1048 .replace((object_id, offset + 1))
1049 .is_some()
1050 {
1051 bail!(anyhow!(FxfsError::Inconsistent).context(
1052 "Multiple EndFlush mutations in a \
1053 single transaction"
1054 ));
1055 }
1056 }
1057 }
1058 Mutation::DeleteVolume => {
1059 if let Some((flushed_object, _)) =
1060 ¤t_transaction.end_flush
1061 {
1062 if *flushed_object == object_id {
1063 bail!(anyhow!(FxfsError::Inconsistent).context(
1064 "Multiple EndFlush/DeleteVolume mutations in a \
1065 single transaction for the same object"
1066 ));
1067 }
1068 }
1069 if current_transaction
1070 .volume_deleted
1071 .replace(object_id)
1072 .is_some()
1073 {
1074 bail!(anyhow!(FxfsError::Inconsistent).context(
1075 "Multiple DeleteVolume mutations in a single \
1076 transaction"
1077 ));
1078 }
1079 stores_deleted.insert(object_id);
1080 }
1081 _ => {}
1082 }
1083
1084 if (object_id_filter == INVALID_OBJECT_ID
1087 || object_id_filter == object_id)
1088 && self.should_apply(object_id, ¤t_transaction.checkpoint)
1089 {
1090 if object_id == root_parent_store_object_id {
1091 current_transaction.root_parent_mutations.push(mutation);
1092 } else if object_id == root_store_object_id {
1093 current_transaction.root_mutations.push(mutation);
1094 } else {
1095 current_transaction
1096 .non_root_mutations
1097 .push((object_id, mutation));
1098 }
1099 }
1100 }
1101 JournalRecord::DataChecksums(device_range, checksums, first_write) => {
1102 let current_transaction = match current_transaction.as_mut() {
1103 None => {
1104 transactions.push(JournaledTransaction::new(checkpoint));
1105 current_transaction = transactions.last_mut();
1106 current_transaction.as_mut().unwrap()
1107 }
1108 Some(transaction) => transaction,
1109 };
1110 current_transaction.checksums.push(JournaledChecksums {
1111 device_range,
1112 checksums,
1113 first_write,
1114 });
1115 }
1116 JournalRecord::Commit => {
1117 if let Some(&mut JournaledTransaction {
1118 ref checkpoint,
1119 ref root_parent_mutations,
1120 ref mut end_offset,
1121 ..
1122 }) = current_transaction.take()
1123 {
1124 for mutation in root_parent_mutations {
1125 if let Mutation::ObjectStore(ObjectStoreMutation {
1129 item:
1130 Item {
1131 key:
1132 ObjectKey {
1133 object_id,
1134 data:
1135 ObjectKeyData::Attribute(
1136 AttributeId::DATA,
1137 AttributeKey::Extent(extent),
1138 ),
1139 ..
1140 },
1141 value:
1142 ObjectValue::Extent(ExtentValue::Some {
1143 device_offset,
1144 ..
1145 }),
1146 ..
1147 },
1148 ..
1149 }) = mutation
1150 {
1151 let handle = reader.handle();
1154 if *object_id != handle.object_id() {
1155 continue;
1156 }
1157 if let Some(end_offset) = handle.end_offset() {
1158 if extent.start != end_offset {
1159 bail!(anyhow!(FxfsError::Inconsistent).context(
1160 format!(
1161 "Unexpected journal extent {:?} -> {}, \
1162 expected start: {}",
1163 *extent, device_offset, end_offset,
1164 )
1165 ));
1166 }
1167 }
1168 handle.push_extent(
1169 checkpoint.file_offset,
1170 *device_offset
1171 ..*device_offset
1172 + extent.length().context("Invalid extent")?,
1173 );
1174 }
1175 }
1176 *end_offset = reader.journal_file_checkpoint().file_offset;
1177 }
1178 }
1179 JournalRecord::Discard(offset) => {
1180 if offset == 0 {
1181 bail!(
1182 anyhow!(FxfsError::Inconsistent)
1183 .context("Invalid offset for Discard")
1184 );
1185 }
1186 if let Some(transaction) = current_transaction.as_ref() {
1187 if transaction.checkpoint.file_offset < offset {
1188 continue;
1190 }
1191 }
1192 current_transaction = None;
1193 while let Some(transaction) = transactions.last() {
1194 if transaction.checkpoint.file_offset < offset {
1195 break;
1196 }
1197 transactions.pop();
1198 }
1199 reader.handle().discard_extents(offset);
1200 }
1201 JournalRecord::DidFlushDevice(offset) => {
1202 if offset > device_flushed_offset {
1203 device_flushed_offset = offset;
1204 }
1205 }
1206 }
1207 }
1208 ReadResult::ChecksumMismatch => break,
1210 }
1211 }
1212
1213 if current_transaction.is_some() {
1215 transactions.pop();
1216 }
1217
1218 Ok(JournaledTransactions { transactions, device_flushed_offset })
1219 }
1220
1221 pub async fn init_empty(&self, filesystem: Arc<FxFilesystem>) -> Result<(), Error> {
1224 const INIT_ROOT_PARENT_STORE_OBJECT_ID: u64 = 3;
1230 const INIT_ROOT_STORE_OBJECT_ID: u64 = 4;
1231 const INIT_ALLOCATOR_OBJECT_ID: u64 = 5;
1232
1233 info!(device_size = filesystem.device().size(); "Formatting");
1234
1235 let checkpoint = JournalCheckpoint {
1236 version: LATEST_VERSION,
1237 ..self.inner.lock().writer.journal_file_checkpoint()
1238 };
1239
1240 let mut current_generation = 1;
1241 if filesystem.options().image_builder_mode.is_some() {
1242 let block_size = filesystem.block_size();
1250 match self.read_superblocks(filesystem.device(), block_size).await {
1251 Ok((super_block, _)) => {
1252 log::info!(
1253 "Found existing superblock with generation {}. Bumping by 1.",
1254 super_block.generation
1255 );
1256 current_generation = super_block.generation.wrapping_add(1);
1257 }
1258 Err(_) => {
1259 }
1263 }
1264 }
1265
1266 let root_parent = ObjectStore::new_empty(
1267 None,
1268 INIT_ROOT_PARENT_STORE_OBJECT_ID,
1269 filesystem.clone(),
1270 Box::new(NullCache {}),
1271 );
1272 self.objects.set_root_parent_store(root_parent.clone());
1273
1274 let allocator = Arc::new(Allocator::new(filesystem.clone(), INIT_ALLOCATOR_OBJECT_ID));
1275 self.objects.set_allocator(allocator.clone());
1276 self.objects.init_metadata_reservation()?;
1277
1278 let journal_handle;
1279 let super_block_a_handle;
1280 let super_block_b_handle;
1281 let root_store;
1282 let mut transaction = root_parent
1283 .new_transaction(
1284 lock_keys![],
1285 Options { skip_journal_checks: true, ..Default::default() },
1286 )
1287 .await?;
1288 root_store = root_parent
1289 .new_child_store(
1290 &mut transaction,
1291 NewChildStoreOptions { object_id: INIT_ROOT_STORE_OBJECT_ID, ..Default::default() },
1292 Box::new(NullCache {}),
1293 )
1294 .await
1295 .context("new_child_store")?;
1296 self.objects.set_root_store(root_store.clone());
1297
1298 allocator.create(&mut transaction).await?;
1299
1300 super_block_a_handle = ObjectStore::create_object_with_id(
1302 &root_store,
1303 &mut transaction,
1304 ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::A.object_id()).unwrap()),
1305 HandleOptions::default(),
1306 None,
1307 )
1308 .context("create super block")?;
1309 root_store.update_last_object_id(SuperBlockInstance::A.object_id());
1310 super_block_a_handle
1311 .extend(&mut transaction, SuperBlockInstance::A.first_extent())
1312 .await
1313 .context("extend super block")?;
1314 super_block_b_handle = ObjectStore::create_object_with_id(
1315 &root_store,
1316 &mut transaction,
1317 ReservedId::new(&root_store, NonZero::new(SuperBlockInstance::B.object_id()).unwrap()),
1318 HandleOptions::default(),
1319 None,
1320 )
1321 .context("create super block")?;
1322 root_store.update_last_object_id(SuperBlockInstance::B.object_id());
1323 super_block_b_handle
1324 .extend(&mut transaction, SuperBlockInstance::B.first_extent())
1325 .await
1326 .context("extend super block")?;
1327
1328 journal_handle = ObjectStore::create_object(
1330 &root_parent,
1331 &mut transaction,
1332 journal_handle_options(),
1333 None,
1334 )
1335 .await
1336 .context("create journal")?;
1337 if self.inner.lock().image_builder_mode.is_none() {
1338 let mut file_range = 0..self.chunk_size();
1339 journal_handle
1340 .preallocate_range(&mut transaction, &mut file_range)
1341 .await
1342 .context("preallocate journal")?;
1343 if file_range.start < file_range.end {
1344 bail!("preallocate_range returned too little space");
1345 }
1346 }
1347
1348 root_store.create(&mut transaction).await?;
1350
1351 root_parent.set_graveyard_directory_object_id(
1353 Graveyard::create(&mut transaction, &root_parent).await?,
1354 );
1355
1356 transaction.commit().await?;
1357
1358 self.inner.lock().super_block_header = SuperBlockHeader::new(
1359 current_generation,
1360 root_parent.store_object_id(),
1361 root_parent.graveyard_directory_object_id(),
1362 root_store.store_object_id(),
1363 allocator.object_id(),
1364 journal_handle.object_id(),
1365 checkpoint,
1366 LATEST_VERSION,
1367 );
1368
1369 let _ = self.handle.set(journal_handle);
1371 Ok(())
1372 }
1373
1374 pub async fn allocate_journal(&self) -> Result<(), Error> {
1377 let handle = self.handle.get().unwrap();
1378 let mut transaction = handle
1379 .store()
1380 .new_transaction(
1381 lock_keys![LockKey::object(handle.store().store_object_id(), handle.object_id()),],
1382 Options { skip_journal_checks: true, ..Default::default() },
1383 )
1384 .await?;
1385 let mut file_range = 0..self.chunk_size();
1386 self.handle
1387 .get()
1388 .unwrap()
1389 .preallocate_range(&mut transaction, &mut file_range)
1390 .await
1391 .context("preallocate journal")?;
1392 if file_range.start < file_range.end {
1393 bail!("preallocate_range returned too little space");
1394 }
1395 transaction.commit().await?;
1396 Ok(())
1397 }
1398
1399 pub async fn init_superblocks(&self) -> Result<(), Error> {
1400 for _ in 0..2 {
1402 self.write_super_block().await?;
1403 }
1404 Ok(())
1405 }
1406
1407 pub async fn read_transactions_for_object(
1413 &self,
1414 object_id: u64,
1415 ) -> Result<Vec<JournaledTransaction>, Error> {
1416 let handle = self.handle.get().expect("No journal handle");
1417 let handle = ObjectStore::open_object(
1419 handle.owner(),
1420 handle.object_id(),
1421 journal_handle_options(),
1422 None,
1423 )
1424 .await?;
1425
1426 let checkpoint = match self.objects.journal_checkpoint(object_id) {
1427 Some(checkpoint) => checkpoint,
1428 None => return Ok(vec![]),
1429 };
1430 let mut reader = JournalReader::new(handle, &checkpoint);
1431 let end_offset = self.inner.lock().valid_to;
1434 Ok(self.read_transactions(&mut reader, Some(end_offset), object_id).await?.transactions)
1435 }
1436
1437 pub async fn commit(&self, transaction: &mut Transaction<'_>) -> Result<u64, Error> {
1439 if transaction.is_empty() {
1440 return Ok(self.inner.lock().writer.journal_file_checkpoint().file_offset);
1441 }
1442
1443 self.pre_commit(transaction).await?;
1444 Ok(self.write_and_apply_mutations(transaction))
1445 }
1446
1447 async fn pre_commit(&self, _transaction: &Transaction<'_>) -> Result<(), Error> {
1450 let handle;
1451
1452 let (size, zero_offset) = {
1453 let mut inner = self.inner.lock();
1454
1455 if std::mem::take(&mut inner.output_reset_version) {
1457 LATEST_VERSION.serialize_into(&mut inner.writer)?;
1458 }
1459
1460 if let Some(discard_offset) = inner.discard_offset {
1461 JournalRecord::Discard(discard_offset).serialize_into(&mut inner.writer)?;
1462 inner.discard_offset = None;
1463 }
1464
1465 if inner.needs_did_flush_device {
1466 let offset = inner.device_flushed_offset;
1467 JournalRecord::DidFlushDevice(offset).serialize_into(&mut inner.writer)?;
1468 inner.needs_did_flush_device = false;
1469 }
1470
1471 handle = match self.handle.get() {
1472 None => return Ok(()),
1473 Some(x) => x,
1474 };
1475
1476 let file_offset = inner.writer.journal_file_checkpoint().file_offset;
1477
1478 let size = handle.get_size();
1479 let size = if file_offset + self.chunk_size() > size { Some(size) } else { None };
1480
1481 if size.is_none()
1482 && inner.zero_offset.is_none()
1483 && !self.objects.needs_borrow_for_journal(file_offset)
1484 {
1485 return Ok(());
1486 }
1487
1488 (size, inner.zero_offset)
1489 };
1490
1491 let mut transaction = handle
1492 .new_transaction_with_options(Options {
1493 skip_journal_checks: true,
1494 borrow_metadata_space: true,
1495 allocator_reservation: Some(self.objects.metadata_reservation()),
1496 ..Default::default()
1497 })
1498 .await?;
1499 if let Some(size) = size {
1500 handle
1501 .preallocate_range(&mut transaction, &mut (size..size + self.chunk_size()))
1502 .await?;
1503 }
1504 if let Some(zero_offset) = zero_offset {
1505 handle.zero(&mut transaction, 0..zero_offset).await?;
1506 }
1507
1508 self.write_and_apply_mutations(&mut transaction);
1511
1512 let mut inner = self.inner.lock();
1513
1514 if let Some(size) = size {
1517 assert!(inner.writer.journal_file_checkpoint().file_offset < size);
1518 }
1519
1520 if inner.zero_offset == zero_offset {
1521 inner.zero_offset = None;
1522 }
1523
1524 Ok(())
1525 }
1526
1527 fn should_apply(&self, object_id: u64, journal_file_checkpoint: &JournalCheckpoint) -> bool {
1531 let super_block_header = &self.inner.lock().super_block_header;
1532 let offset = super_block_header
1533 .journal_file_offsets
1534 .get(&object_id)
1535 .cloned()
1536 .unwrap_or(super_block_header.super_block_journal_file_offset);
1537 journal_file_checkpoint.file_offset >= offset
1538 }
1539
1540 async fn write_super_block(&self) -> Result<(), Error> {
1543 let root_parent_store = self.objects.root_parent_store();
1544
1545 let old_layers;
1550 let old_super_block_offset;
1551 let mut new_super_block_header;
1552 let checkpoint;
1553 let borrowed;
1554
1555 {
1556 let _sync_guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1557 {
1558 let _write_guard = self.writer_mutex.lock();
1559 (checkpoint, borrowed) = self.pad_to_block()?;
1560 old_layers = super_block::compact_root_parent(&*root_parent_store)?;
1561 }
1562 self.flush_device(checkpoint.file_offset)
1563 .await
1564 .context("flush failed when writing superblock")?;
1565 }
1566
1567 new_super_block_header = self.inner.lock().super_block_header.clone();
1568
1569 old_super_block_offset = new_super_block_header.journal_checkpoint.file_offset;
1570
1571 let (journal_file_offsets, min_checkpoint) = self.objects.journal_file_offsets();
1572
1573 new_super_block_header.generation = new_super_block_header.generation.wrapping_add(1);
1574 new_super_block_header.super_block_journal_file_offset = checkpoint.file_offset;
1575 new_super_block_header.journal_checkpoint = min_checkpoint.unwrap_or(checkpoint);
1576 new_super_block_header.journal_checkpoint.version = LATEST_VERSION;
1577 new_super_block_header.journal_file_offsets = journal_file_offsets;
1578 new_super_block_header.borrowed_metadata_space = borrowed;
1579
1580 self.super_block_manager
1581 .save(
1582 new_super_block_header.clone(),
1583 self.objects.root_parent_store().filesystem(),
1584 old_layers,
1585 )
1586 .await?;
1587 {
1588 let mut inner = self.inner.lock();
1589 inner.super_block_header = new_super_block_header;
1590 inner.zero_offset = Some(round_down(old_super_block_offset, BLOCK_SIZE));
1591 }
1592
1593 Ok(())
1594 }
1595
1596 pub async fn sync(
1603 &self,
1604 options: SyncOptions<'_>,
1605 ) -> Result<Option<(JournalCheckpoint, u64)>, Error> {
1606 let _guard = debug_assert_not_too_long!(self.sync_mutex.lock());
1607
1608 let (checkpoint, borrowed) = {
1609 if let Some(precondition) = options.precondition {
1610 if !precondition() {
1611 return Ok(None);
1612 }
1613 }
1614
1615 let _guard = self.writer_mutex.lock();
1618
1619 self.pad_to_block()?
1620 };
1621
1622 if options.flush_device {
1623 self.flush_device(checkpoint.file_offset).await.context("sync: flush failed")?;
1624 }
1625
1626 Ok(Some((checkpoint, borrowed)))
1627 }
1628
1629 fn pad_to_block(&self) -> Result<(JournalCheckpoint, u64), Error> {
1633 let mut inner = self.inner.lock();
1634 let checkpoint = inner.writer.journal_file_checkpoint();
1635 if checkpoint.file_offset % BLOCK_SIZE != 0 {
1636 JournalRecord::EndBlock.serialize_into(&mut inner.writer)?;
1637 inner.writer.pad_to_block()?;
1638 if let Some(waker) = inner.flush_waker.take() {
1639 waker.wake();
1640 }
1641 }
1642 Ok((checkpoint, self.objects.borrowed_metadata_space()))
1643 }
1644
1645 async fn flush_device(&self, checkpoint_offset: u64) -> Result<(), Error> {
1646 assert!(
1647 self.inner.lock().image_builder_mode.is_none(),
1648 "flush_device called in image builder mode"
1649 );
1650 debug_assert_not_too_long!(poll_fn(|ctx| {
1651 let mut inner = self.inner.lock();
1652 if inner.flushed_offset >= checkpoint_offset {
1653 Poll::Ready(Ok(()))
1654 } else if inner.terminate {
1655 let context = inner
1656 .terminate_reason
1657 .as_ref()
1658 .map(|e| format!("Journal closed with error: {:?}", e))
1659 .unwrap_or_else(|| "Journal closed".to_string());
1660 Poll::Ready(Err(anyhow!(FxfsError::JournalFlushError).context(context)))
1661 } else {
1662 inner.sync_waker = Some(ctx.waker().clone());
1663 Poll::Pending
1664 }
1665 }))?;
1666
1667 let needs_flush = self.inner.lock().device_flushed_offset < checkpoint_offset;
1668 if needs_flush {
1669 let trace = self.trace.load(Ordering::Relaxed);
1670 if trace {
1671 info!("J: start flush device");
1672 }
1673 self.handle.get().unwrap().flush_device().await?;
1674 if trace {
1675 info!("J: end flush device");
1676 }
1677
1678 {
1686 let mut inner = self.inner.lock();
1687 inner.device_flushed_offset = checkpoint_offset;
1688 inner.needs_did_flush_device = true;
1689 }
1690
1691 self.objects.allocator().did_flush_device(checkpoint_offset);
1694 if trace {
1695 info!("J: did flush device");
1696 }
1697 }
1698
1699 Ok(())
1700 }
1701
1702 pub fn super_block_header(&self) -> SuperBlockHeader {
1704 self.inner.lock().super_block_header.clone()
1705 }
1706
1707 pub async fn check_journal_space(&self) -> Result<(), Error> {
1709 loop {
1710 debug_assert_not_too_long!({
1711 let inner = self.inner.lock();
1712 if inner.terminate {
1713 let context = inner
1716 .terminate_reason
1717 .as_ref()
1718 .map(|e| format!("Journal closed with error: {:?}", e))
1719 .unwrap_or_else(|| "Journal closed".to_string());
1720 break Err(anyhow!(FxfsError::JournalFlushError).context(context));
1721 }
1722 if self.objects.last_end_offset()
1723 - inner.super_block_header.journal_checkpoint.file_offset
1724 < inner.reclaim_size
1725 {
1726 break Ok(());
1727 }
1728 if inner.image_builder_mode.is_some() {
1729 break Ok(());
1730 }
1731 if inner.disable_compactions {
1732 break Err(
1733 anyhow!(FxfsError::JournalFlushError).context("Compactions disabled")
1734 );
1735 }
1736 self.reclaim_event.listen()
1737 });
1738 }
1739 }
1740
1741 fn chunk_size(&self) -> u64 {
1742 CHUNK_SIZE
1743 }
1744
1745 fn write_and_apply_mutations(&self, transaction: &mut Transaction<'_>) -> u64 {
1746 let checkpoint_before;
1747 let checkpoint_after;
1748 {
1749 let _guard = self.writer_mutex.lock();
1750 checkpoint_before = {
1751 let mut inner = self.inner.lock();
1752 if transaction.includes_write() {
1753 inner.needs_barrier = true;
1754 }
1755 let checkpoint = inner.writer.journal_file_checkpoint();
1756 for TxnMutation { object_id, mutation, .. } in transaction.mutations() {
1757 self.objects.write_mutation(
1758 *object_id,
1759 mutation,
1760 Writer(*object_id, &mut inner.writer),
1761 );
1762 }
1763 checkpoint
1764 };
1765 let maybe_mutation =
1766 self.objects.apply_transaction(transaction, &checkpoint_before).expect(
1767 "apply_transaction should not fail in live mode; \
1768 filesystem will be in an inconsistent state",
1769 );
1770 checkpoint_after = {
1771 let mut inner = self.inner.lock();
1772 if let Some(mutation) = maybe_mutation {
1773 inner
1774 .writer
1775 .write_record(&JournalRecord::Mutation { object_id: 0, mutation })
1776 .unwrap();
1777 }
1778 for (device_range, checksums, first_write) in
1779 transaction.take_checksums().into_iter()
1780 {
1781 inner
1782 .writer
1783 .write_record(&JournalRecord::DataChecksums(
1784 device_range,
1785 Checksums::fletcher(checksums),
1786 first_write,
1787 ))
1788 .unwrap();
1789 }
1790 inner.writer.write_record(&JournalRecord::Commit).unwrap();
1791
1792 inner.writer.journal_file_checkpoint()
1793 };
1794 }
1795 self.objects.did_commit_transaction(
1796 transaction,
1797 &checkpoint_before,
1798 checkpoint_after.file_offset,
1799 );
1800
1801 if let Some(waker) = self.inner.lock().flush_waker.take() {
1802 waker.wake();
1803 }
1804
1805 checkpoint_before.file_offset
1806 }
1807
1808 pub async fn flush_task(self: Arc<Self>) {
1812 let mut flush_fut = None;
1813 let mut compact_fut = None;
1814 let mut flush_error = false;
1815 poll_fn(|ctx| {
1816 loop {
1817 {
1818 let mut inner = self.inner.lock();
1819 if flush_fut.is_none() && !flush_error && self.handle.get().is_some() {
1820 let flushable = inner.writer.flushable_bytes();
1821 if flushable > 0 {
1822 flush_fut = Some(Box::pin(self.flush(flushable)));
1823 }
1824 }
1825 if inner.terminate && flush_fut.is_none() && compact_fut.is_none() {
1826 return Poll::Ready(());
1827 }
1828 let journal_bytes = self.objects.last_end_offset()
1833 - inner.super_block_header.journal_checkpoint.file_offset;
1834 fxfs_trace::counter!("journal-bytes", 0, "total" => journal_bytes);
1835 if compact_fut.is_none()
1839 && !inner.terminate
1840 && !inner.disable_compactions
1841 && inner.image_builder_mode.is_none()
1842 && journal_bytes > inner.reclaim_size / 2
1843 {
1844 compact_fut = Some(Box::pin(self.compact()));
1845 inner.compaction_running = true;
1846 }
1847 inner.flush_waker = Some(ctx.waker().clone());
1848 }
1849 let mut pending = true;
1850 if let Some(fut) = flush_fut.as_mut() {
1851 if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1852 if let Err(e) = result {
1853 self.inner.lock().terminate(Some(e.context("Flush error")));
1854 self.reclaim_event.notify(usize::MAX);
1855 flush_error = true;
1856 }
1857 flush_fut = None;
1858 pending = false;
1859 }
1860 }
1861 if let Some(fut) = compact_fut.as_mut() {
1862 if let Poll::Ready(result) = fut.poll_unpin(ctx) {
1863 let mut inner = self.inner.lock();
1864 if let Err(e) = result {
1865 inner.terminate(Some(e.context("Compaction error")));
1866 }
1867 compact_fut = None;
1868 inner.compaction_running = false;
1869 self.reclaim_event.notify(usize::MAX);
1870 pending = false;
1871 fxfs_trace::counter!(
1872 "journal-bytes",
1873 0,
1874 "total" => self.objects.last_end_offset()
1875 - inner.super_block_header.journal_checkpoint.file_offset
1876 );
1877 }
1878 }
1879 if pending {
1880 return Poll::Pending;
1881 }
1882 }
1883 })
1884 .await;
1885 }
1886
1887 pub fn get_compaction_yielder(&self) -> CompactionYielder<'_> {
1889 CompactionYielder::new(self)
1890 }
1891
1892 async fn flush(&self, amount: usize) -> Result<(), Error> {
1893 let handle = self.handle.get().unwrap();
1894 let mut buf = handle.allocate_buffer(amount).await;
1895 let (offset, len, barrier_on_first_write) = {
1896 let mut inner = self.inner.lock();
1897 let offset = inner.writer.take_flushable(buf.as_mut());
1898 let barrier_on_first_write = inner.needs_barrier && inner.barriers_enabled;
1899 inner.needs_barrier = false;
1902 (offset, buf.len() as u64, barrier_on_first_write)
1903 };
1904 self.handle
1905 .get()
1906 .unwrap()
1907 .overwrite(
1908 offset,
1909 buf.as_mut(),
1910 OverwriteOptions { barrier_on_first_write, ..Default::default() },
1911 )
1912 .await?;
1913
1914 let mut inner = self.inner.lock();
1915 if let Some(waker) = inner.sync_waker.take() {
1916 waker.wake();
1917 }
1918 inner.flushed_offset = offset + len;
1919 inner.valid_to = inner.flushed_offset;
1920 Ok(())
1921 }
1922
1923 #[trace]
1924 async fn compact(&self) -> Result<(), Error> {
1925 assert!(
1926 self.inner.lock().image_builder_mode.is_none(),
1927 "compact called in image builder mode"
1928 );
1929 let bytes_before = self.objects.compaction_bytes_written();
1930 let _measure = crate::metrics::DurationMeasureScope::new(
1931 &crate::metrics::lsm_tree_metrics().journal_compaction_time,
1932 );
1933 crate::metrics::lsm_tree_metrics().journal_compactions_total.add(1);
1934 let trace = self.trace.load(Ordering::Relaxed);
1935 debug!("Compaction starting");
1936 if trace {
1937 info!("J: start compaction");
1938 }
1939 let earliest_version = self.objects.flush().await.context("Failed to flush objects")?;
1940 self.inner.lock().super_block_header.earliest_version = earliest_version;
1941 self.write_super_block().await.context("Failed to write superblock")?;
1942 if trace {
1943 info!("J: end compaction");
1944 }
1945 debug!("Compaction finished");
1946 let bytes_after = self.objects.compaction_bytes_written();
1947 crate::metrics::lsm_tree_metrics()
1948 .journal_compaction_bytes_written
1949 .add(bytes_after.saturating_sub(bytes_before));
1950 Ok(())
1951 }
1952
1953 pub async fn force_compact(&self) -> Result<(), Error> {
1956 self.inner.lock().forced_compaction = true;
1957 scopeguard::defer! { self.inner.lock().forced_compaction = false; }
1958 self.compact().await
1959 }
1960
1961 pub async fn stop_compactions(&self) {
1962 loop {
1963 debug_assert_not_too_long!({
1964 let mut inner = self.inner.lock();
1965 inner.disable_compactions = true;
1966 if !inner.compaction_running {
1967 return;
1968 }
1969 self.reclaim_event.listen()
1970 });
1971 }
1972 }
1973
1974 pub fn track_statistics(self: &Arc<Self>, parent: &fuchsia_inspect::Node, name: &str) {
1977 let this = Arc::downgrade(self);
1978 parent.record_lazy_child(name, move || {
1979 let this_clone = this.clone();
1980 async move {
1981 let inspector = fuchsia_inspect::Inspector::default();
1982 if let Some(this) = this_clone.upgrade() {
1983 let (journal_min, journal_max, journal_reclaim_size) = {
1984 let inner = this.inner.lock();
1986 (
1987 round_down(
1988 inner.super_block_header.journal_checkpoint.file_offset,
1989 BLOCK_SIZE,
1990 ),
1991 inner.flushed_offset,
1992 inner.reclaim_size,
1993 )
1994 };
1995 let root = inspector.root();
1996 root.record_uint("journal_min_offset", journal_min);
1997 root.record_uint("journal_max_offset", journal_max);
1998 root.record_uint("journal_size", journal_max - journal_min);
1999 root.record_uint("journal_reclaim_size", journal_reclaim_size);
2000
2001 if let Some(x) = round_div(
2003 100 * (journal_max - journal_min),
2004 this.objects.allocator().get_disk_bytes(),
2005 ) {
2006 root.record_uint("journal_size_to_disk_size_percent", x);
2007 }
2008 }
2009 Ok(inspector)
2010 }
2011 .boxed()
2012 });
2013 }
2014
2015 pub fn terminate(&self) {
2017 self.inner.lock().terminate(None);
2018 self.reclaim_event.notify(usize::MAX);
2019 }
2020}
2021
2022pub struct Writer<'a>(u64, &'a mut JournalWriter);
2024
2025impl Writer<'_> {
2026 pub fn write(&mut self, mutation: Mutation) {
2027 self.1.write_record(&JournalRecord::Mutation { object_id: self.0, mutation }).unwrap();
2028 }
2029}
2030
2031#[cfg(target_os = "fuchsia")]
2032mod yielder {
2033 use super::Journal;
2034 use crate::lsm_tree::Yielder;
2035 use fuchsia_async as fasync;
2036
2037 pub struct CompactionYielder<'a> {
2041 journal: &'a Journal,
2042 low_priority_task: Option<fasync::LowPriorityTask>,
2043 }
2044
2045 impl<'a> CompactionYielder<'a> {
2046 pub fn new(journal: &'a Journal) -> Self {
2047 Self { journal, low_priority_task: None }
2048 }
2049 }
2050
2051 impl Yielder for CompactionYielder<'_> {
2052 async fn yield_now(&mut self) {
2053 const IDLE_PERIOD: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(4);
2057 const MAX_YIELD_DURATION: zx::MonotonicDuration =
2058 zx::MonotonicDuration::from_millis(16);
2059
2060 {
2061 let inner = self.journal.inner.lock();
2062 if inner.forced_compaction {
2063 return;
2064 }
2065 let outstanding = self.journal.objects.last_end_offset()
2066 - inner.super_block_header.journal_checkpoint.file_offset;
2067 let half_reclaim_size = inner.reclaim_size / 2;
2068 if outstanding
2069 .checked_sub(half_reclaim_size)
2070 .is_some_and(|x| x >= half_reclaim_size / 2)
2071 {
2072 self.low_priority_task = None;
2076 return;
2077 }
2078 }
2079
2080 self.low_priority_task
2081 .get_or_insert_with(|| fasync::LowPriorityTask::new())
2082 .wait_until_idle_for(
2083 IDLE_PERIOD,
2084 fasync::MonotonicInstant::after(MAX_YIELD_DURATION),
2085 )
2086 .await;
2087 }
2088 }
2089}
2090
2091#[cfg(not(target_os = "fuchsia"))]
2092mod yielder {
2093 use super::Journal;
2094 use crate::lsm_tree::Yielder;
2095
2096 #[expect(dead_code)]
2097 pub struct CompactionYielder<'a>(&'a Journal);
2098
2099 impl<'a> CompactionYielder<'a> {
2100 pub fn new(journal: &'a Journal) -> Self {
2101 Self(journal)
2102 }
2103 }
2104
2105 impl Yielder for CompactionYielder<'_> {
2106 async fn yield_now(&mut self) {}
2107 }
2108}
2109
2110pub use yielder::*;
2111
2112#[cfg(test)]
2113mod tests {
2114 use super::SuperBlockInstance;
2115 use crate::filesystem::{FxFilesystem, FxFilesystemBuilder, SyncOptions};
2116 use crate::fsck::fsck;
2117 use crate::object_handle::{ObjectHandle, ReadObjectHandle, WriteObjectHandle};
2118 use crate::object_store::directory::Directory;
2119 use crate::object_store::transaction::Options;
2120 use crate::object_store::volume::root_volume;
2121 use crate::object_store::{
2122 HandleOptions, LockKey, NewChildStoreOptions, ObjectStore, StoreOptions, lock_keys,
2123 };
2124 #[cfg(target_os = "fuchsia")]
2125 use fuchsia_async::TestExecutor;
2126 use fuchsia_async::{self as fasync, MonotonicDuration};
2127 use storage_device::DeviceHolder;
2128 use storage_device::fake_device::FakeDevice;
2129
2130 const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
2131
2132 #[fuchsia::test]
2133 async fn test_replay() {
2134 const TEST_DATA: &[u8] = b"hello";
2135
2136 let device = DeviceHolder::new(FakeDevice::new(8192, TEST_DEVICE_BLOCK_SIZE));
2137
2138 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2139
2140 let object_id = {
2141 let root_store = fs.root_store();
2142 let root_directory =
2143 Directory::open(&root_store, root_store.root_directory_object_id())
2144 .await
2145 .expect("open failed");
2146 let mut transaction = fs
2147 .root_store()
2148 .new_transaction(
2149 lock_keys![LockKey::object(
2150 root_store.store_object_id(),
2151 root_store.root_directory_object_id(),
2152 )],
2153 Options::default(),
2154 )
2155 .await
2156 .expect("new_transaction failed");
2157 let handle = root_directory
2158 .create_child_file(&mut transaction, "test")
2159 .await
2160 .expect("create_child_file failed");
2161
2162 transaction.commit().await.expect("commit failed");
2163 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2164 buf.as_mut_slice().copy_from_slice(TEST_DATA);
2165 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2166 fs.sync(SyncOptions::default()).await.expect("sync failed");
2169 handle.object_id()
2170 };
2171
2172 {
2173 fs.close().await.expect("Close failed");
2174 let device = fs.take_device().await;
2175 device.reopen(false);
2176 let fs = FxFilesystem::open(device).await.expect("open failed");
2177 let handle = ObjectStore::open_object(
2178 &fs.root_store(),
2179 object_id,
2180 HandleOptions::default(),
2181 None,
2182 )
2183 .await
2184 .expect("open_object failed");
2185 let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2186 assert_eq!(handle.read(0, buf.as_mut()).await.expect("read failed"), TEST_DATA.len());
2187 assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2188 fsck(fs.clone()).await.expect("fsck failed");
2189 fs.close().await.expect("Close failed");
2190 }
2191 }
2192
2193 #[fuchsia::test]
2194 async fn test_reset() {
2195 const TEST_DATA: &[u8] = b"hello";
2196
2197 let device = DeviceHolder::new(FakeDevice::new(32768, TEST_DEVICE_BLOCK_SIZE));
2198
2199 let mut object_ids = Vec::new();
2200
2201 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2202 {
2203 let root_store = fs.root_store();
2204 let root_directory =
2205 Directory::open(&root_store, root_store.root_directory_object_id())
2206 .await
2207 .expect("open failed");
2208 let mut transaction = fs
2209 .root_store()
2210 .new_transaction(
2211 lock_keys![LockKey::object(
2212 root_store.store_object_id(),
2213 root_store.root_directory_object_id(),
2214 )],
2215 Options::default(),
2216 )
2217 .await
2218 .expect("new_transaction failed");
2219 let handle = root_directory
2220 .create_child_file(&mut transaction, "test")
2221 .await
2222 .expect("create_child_file failed");
2223 transaction.commit().await.expect("commit failed");
2224 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2225 buf.as_mut_slice().copy_from_slice(TEST_DATA);
2226 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2227 fs.sync(SyncOptions::default()).await.expect("sync failed");
2228 object_ids.push(handle.object_id());
2229
2230 for i in 0..1000 {
2233 let mut transaction = fs
2234 .root_store()
2235 .new_transaction(
2236 lock_keys![LockKey::object(
2237 root_store.store_object_id(),
2238 root_store.root_directory_object_id(),
2239 )],
2240 Options::default(),
2241 )
2242 .await
2243 .expect("new_transaction failed");
2244 let handle = root_directory
2245 .create_child_file(&mut transaction, &format!("{}", i))
2246 .await
2247 .expect("create_child_file failed");
2248 transaction.commit().await.expect("commit failed");
2249 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2250 buf.as_mut_slice().copy_from_slice(TEST_DATA);
2251 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2252 object_ids.push(handle.object_id());
2253 }
2254 }
2255 fs.close().await.expect("fs close failed");
2256 let device = fs.take_device().await;
2257 device.reopen(false);
2258 let fs = FxFilesystem::open(device).await.expect("open failed");
2259 fsck(fs.clone()).await.expect("fsck failed");
2260 {
2261 let root_store = fs.root_store();
2262 for &object_id in &object_ids[0..1] {
2264 let handle = ObjectStore::open_object(
2265 &root_store,
2266 object_id,
2267 HandleOptions::default(),
2268 None,
2269 )
2270 .await
2271 .expect("open_object failed");
2272 let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2273 assert_eq!(
2274 handle.read(0, buf.as_mut()).await.expect("read failed"),
2275 TEST_DATA.len()
2276 );
2277 assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2278 }
2279
2280 let root_directory =
2282 Directory::open(&root_store, root_store.root_directory_object_id())
2283 .await
2284 .expect("open failed");
2285 let mut transaction = fs
2286 .root_store()
2287 .new_transaction(
2288 lock_keys![LockKey::object(
2289 root_store.store_object_id(),
2290 root_store.root_directory_object_id(),
2291 )],
2292 Options::default(),
2293 )
2294 .await
2295 .expect("new_transaction failed");
2296 let handle = root_directory
2297 .create_child_file(&mut transaction, "test2")
2298 .await
2299 .expect("create_child_file failed");
2300 transaction.commit().await.expect("commit failed");
2301 let mut buf = handle.allocate_buffer(TEST_DATA.len()).await;
2302 buf.as_mut_slice().copy_from_slice(TEST_DATA);
2303 handle.write_or_append(Some(0), buf.as_ref()).await.expect("write failed");
2304 fs.sync(SyncOptions::default()).await.expect("sync failed");
2305 object_ids.push(handle.object_id());
2306 }
2307
2308 fs.close().await.expect("close failed");
2309 let device = fs.take_device().await;
2310 device.reopen(false);
2311 let fs = FxFilesystem::open(device).await.expect("open failed");
2312 {
2313 fsck(fs.clone()).await.expect("fsck failed");
2314
2315 for &object_id in object_ids[0..1].iter().chain(object_ids.last().cloned().iter()) {
2317 let handle = ObjectStore::open_object(
2318 &fs.root_store(),
2319 object_id,
2320 HandleOptions::default(),
2321 None,
2322 )
2323 .await
2324 .unwrap_or_else(|e| {
2325 panic!("open_object failed (object_id: {}): {:?}", object_id, e)
2326 });
2327 let mut buf = handle.allocate_buffer(TEST_DEVICE_BLOCK_SIZE as usize).await;
2328 assert_eq!(
2329 handle.read(0, buf.as_mut()).await.expect("read failed"),
2330 TEST_DATA.len()
2331 );
2332 assert_eq!(&buf.as_slice()[..TEST_DATA.len()], TEST_DATA);
2333 }
2334 }
2335 fs.close().await.expect("close failed");
2336 }
2337
2338 #[fuchsia::test]
2339 async fn test_discard() {
2340 let device = {
2341 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2342 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2343 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2344
2345 let store = root_volume
2346 .new_volume("test", NewChildStoreOptions::default())
2347 .await
2348 .expect("new_volume failed");
2349 let root_directory = Directory::open(&store, store.root_directory_object_id())
2350 .await
2351 .expect("open failed");
2352
2353 let mut i = 0;
2355 loop {
2356 let mut transaction = fs
2357 .root_store()
2358 .new_transaction(
2359 lock_keys![LockKey::object(
2360 store.store_object_id(),
2361 store.root_directory_object_id()
2362 )],
2363 Options::default(),
2364 )
2365 .await
2366 .expect("new_transaction failed");
2367 root_directory
2368 .create_child_file(&mut transaction, &format!("a {i}"))
2369 .await
2370 .expect("create_child_file failed");
2371 if transaction.commit().await.expect("commit failed") > super::CHUNK_SIZE {
2372 break;
2373 }
2374 i += 1;
2375 }
2376
2377 fs.journal().force_compact().await.expect("compact failed");
2379 fs.journal().stop_compactions().await;
2380
2381 let mut i = 0;
2383 loop {
2384 let mut transaction = fs
2385 .root_store()
2386 .new_transaction(
2387 lock_keys![LockKey::object(
2388 store.store_object_id(),
2389 store.root_directory_object_id()
2390 )],
2391 Options::default(),
2392 )
2393 .await
2394 .expect("new_transaction failed");
2395 root_directory
2396 .create_child_file(&mut transaction, &format!("b {i}"))
2397 .await
2398 .expect("create_child_file failed");
2399 if transaction.commit().await.expect("commit failed") > 2 * super::CHUNK_SIZE {
2400 break;
2401 }
2402 i += 1;
2403 }
2404
2405 fasync::Timer::new(MonotonicDuration::from_millis(10)).await;
2407 fs.device().snapshot().expect("snapshot failed")
2410 };
2411
2412 let fs = FxFilesystem::open(device).await.expect("open failed");
2413
2414 {
2415 let root_volume = root_volume(fs.clone()).await.expect("root_volume failed");
2416
2417 let store =
2418 root_volume.volume("test", StoreOptions::default()).await.expect("volume failed");
2419
2420 let root_directory = Directory::open(&store, store.root_directory_object_id())
2421 .await
2422 .expect("open failed");
2423
2424 let mut transaction = fs
2426 .root_store()
2427 .new_transaction(
2428 lock_keys![LockKey::object(
2429 store.store_object_id(),
2430 store.root_directory_object_id()
2431 )],
2432 Options::default(),
2433 )
2434 .await
2435 .expect("new_transaction failed");
2436 root_directory
2437 .create_child_file(&mut transaction, &format!("d"))
2438 .await
2439 .expect("create_child_file failed");
2440 transaction.commit().await.expect("commit failed");
2441 }
2442
2443 fs.close().await.expect("close failed");
2444 let device = fs.take_device().await;
2445 device.reopen(false);
2446
2447 let fs = FxFilesystem::open(device).await.expect("open failed");
2448 fsck(fs.clone()).await.expect("fsck failed");
2449 fs.close().await.expect("close failed");
2450 }
2451
2452 #[fuchsia::test]
2453 async fn test_use_existing_generation() {
2454 let device = DeviceHolder::new(FakeDevice::new(8192, 4096));
2455
2456 let fs = FxFilesystemBuilder::new()
2458 .format(true)
2459 .image_builder_mode(Some(SuperBlockInstance::A))
2460 .open(device)
2461 .await
2462 .expect("open failed");
2463 fs.enable_allocations();
2464 let generation0 = fs.super_block_header().generation;
2465 assert_eq!(generation0, 1);
2466 fs.close().await.expect("close failed");
2467 let device = fs.take_device().await;
2468 device.reopen(false);
2469
2470 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2472 let generation1 = fs.super_block_header().generation;
2473 {
2474 let root_volume = crate::object_store::volume::root_volume(fs.clone())
2475 .await
2476 .expect("root_volume failed");
2477 root_volume
2478 .new_volume("test", crate::object_store::NewChildStoreOptions::default())
2479 .await
2480 .expect("new_volume failed");
2481 }
2482 fs.close().await.expect("close failed");
2483 let device = fs.take_device().await;
2484 device.reopen(false);
2485
2486 let fs = FxFilesystemBuilder::new()
2488 .format(true)
2489 .image_builder_mode(Some(SuperBlockInstance::A))
2490 .open(device)
2491 .await
2492 .expect("open failed");
2493 fs.enable_allocations();
2494 let generation2 = fs.super_block_header().generation;
2495 assert!(
2496 generation2 > generation1,
2497 "generation2 ({}) should be greater than generation1 ({})",
2498 generation2,
2499 generation1
2500 );
2501 fs.close().await.expect("close failed");
2502 }
2503
2504 #[fuchsia::test]
2505 async fn test_image_builder_mode_generation_bump_512_byte_block() {
2506 let device = DeviceHolder::new(FakeDevice::new(16384, 512));
2507
2508 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2510 let generation1 = fs.super_block_header().generation;
2511 fs.close().await.expect("close failed");
2512 let device = fs.take_device().await;
2513 device.reopen(false);
2514
2515 let fs = FxFilesystemBuilder::new()
2517 .format(true)
2518 .image_builder_mode(Some(SuperBlockInstance::A))
2519 .open(device)
2520 .await
2521 .expect("open failed");
2522
2523 fs.enable_allocations();
2524 let generation2 = fs.super_block_header().generation;
2525 assert!(
2526 generation2 > generation1,
2527 "Expected generation bump, got {} vs {}",
2528 generation2,
2529 generation1
2530 );
2531 fs.close().await.expect("close failed");
2532 }
2533
2534 #[fuchsia::test]
2535 #[cfg(target_os = "fuchsia")]
2536 fn test_low_priority_compaction() {
2537 let mut executor = TestExecutor::new_with_fake_time();
2538 let mut fut = std::pin::pin!(async {
2539 use std::sync::Arc;
2540 use std::sync::atomic::{AtomicBool, Ordering};
2541
2542 let device = DeviceHolder::new(FakeDevice::new(16384, TEST_DEVICE_BLOCK_SIZE));
2543 let fs = FxFilesystemBuilder::new()
2544 .journal_options(super::JournalOptions {
2545 reclaim_size: 65536,
2546 ..Default::default()
2547 })
2548 .format(true)
2549 .open(device)
2550 .await
2551 .expect("open failed");
2552
2553 let _low = fasync::LowPriorityTask::new();
2554
2555 {
2557 let root_store = fs.root_store();
2558 let root_directory =
2559 Directory::open(&root_store, root_store.root_directory_object_id())
2560 .await
2561 .expect("open failed");
2562 for i in 0..100 {
2563 let mut transaction = fs
2564 .root_store()
2565 .new_transaction(
2566 lock_keys![LockKey::object(
2567 root_store.store_object_id(),
2568 root_store.root_directory_object_id(),
2569 )],
2570 Options::default(),
2571 )
2572 .await
2573 .expect("new_transaction failed");
2574 root_directory
2575 .create_child_file(&mut transaction, &format!("test{}", i))
2576 .await
2577 .expect("create_child_file failed");
2578 transaction.commit().await.expect("commit failed");
2579 }
2580 }
2581
2582 let stop = Arc::new(AtomicBool::new(false));
2584 let stop_clone = stop.clone();
2585 let _normal_task = fasync::Task::spawn(async move {
2586 while !stop_clone.load(Ordering::Relaxed) {
2587 fasync::Timer::new(fasync::MonotonicInstant::after(
2588 MonotonicDuration::from_millis(1),
2589 ))
2590 .await;
2591 }
2592 });
2593
2594 {
2597 let root_store = fs.root_store();
2598 let root_directory =
2599 Directory::open(&root_store, root_store.root_directory_object_id())
2600 .await
2601 .expect("open failed");
2602 let mut i = 0;
2603 loop {
2604 let mut transaction = fs
2605 .root_store()
2606 .new_transaction(
2607 lock_keys![LockKey::object(
2608 root_store.store_object_id(),
2609 root_store.root_directory_object_id(),
2610 )],
2611 Options::default(),
2612 )
2613 .await
2614 .expect("new_transaction failed");
2615 root_directory
2616 .create_child_file(&mut transaction, &format!("trigger{i}"))
2617 .await
2618 .expect("create_child_file failed");
2619 transaction.commit().await.expect("commit failed");
2620
2621 if fs.journal().inner.lock().compaction_running {
2622 break;
2623 }
2624 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2625 MonotonicDuration::from_millis(1),
2626 ))
2627 .await;
2628 i += 1;
2629 }
2630 }
2631
2632 for _ in 0..10 {
2635 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2636 MonotonicDuration::from_millis(1),
2637 ))
2638 .await;
2639 assert!(fs.journal().inner.lock().compaction_running);
2640 }
2641
2642 stop.store(true, Ordering::Relaxed);
2644 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2645 MonotonicDuration::from_millis(1),
2646 ))
2647 .await;
2648
2649 assert!(fs.journal().inner.lock().compaction_running);
2652
2653 for _ in 0..3 {
2655 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2656 MonotonicDuration::from_millis(1),
2657 ))
2658 .await;
2659 assert!(fs.journal().inner.lock().compaction_running);
2660 }
2661
2662 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2664 MonotonicDuration::from_millis(1),
2665 ))
2666 .await;
2667
2668 let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2670 assert!(!fs.journal().inner.lock().compaction_running);
2671
2672 fs.close().await.expect("Close failed");
2673 });
2674 assert!(executor.run_until_stalled(&mut fut).is_ready());
2675 }
2676
2677 #[fuchsia::test]
2678 #[cfg(target_os = "fuchsia")]
2679 fn test_low_priority_compaction_deadline() {
2680 let mut executor = TestExecutor::new_with_fake_time();
2681 let mut fut = std::pin::pin!(async {
2682 use std::sync::Arc;
2683 use std::sync::atomic::{AtomicBool, Ordering};
2684
2685 let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2686 let fs = FxFilesystemBuilder::new()
2687 .journal_options(super::JournalOptions {
2688 reclaim_size: 65536,
2689 ..Default::default()
2690 })
2691 .format(true)
2692 .open(device)
2693 .await
2694 .expect("open failed");
2695
2696 let _low = fasync::LowPriorityTask::new();
2697
2698 {
2700 let root_store = fs.root_store();
2701 let root_directory =
2702 Directory::open(&root_store, root_store.root_directory_object_id())
2703 .await
2704 .expect("open failed");
2705 for i in 0..10 {
2706 let mut transaction = fs
2707 .root_store()
2708 .new_transaction(
2709 lock_keys![LockKey::object(
2710 root_store.store_object_id(),
2711 root_store.root_directory_object_id(),
2712 )],
2713 Options::default(),
2714 )
2715 .await
2716 .expect("new_transaction failed");
2717 root_directory
2718 .create_child_file(&mut transaction, &format!("test{}", i))
2719 .await
2720 .expect("create_child_file failed");
2721 transaction.commit().await.expect("commit failed");
2722 }
2723 }
2724
2725 let stop = Arc::new(AtomicBool::new(false));
2727 let stop_clone = stop.clone();
2728 let _normal_task = fasync::Task::spawn(async move {
2729 while !stop_clone.load(Ordering::Relaxed) {
2730 fasync::Timer::new(fasync::MonotonicInstant::after(
2731 MonotonicDuration::from_millis(3),
2732 ))
2733 .await;
2734 }
2735 });
2736
2737 {
2739 let root_store = fs.root_store();
2740 let root_directory =
2741 Directory::open(&root_store, root_store.root_directory_object_id())
2742 .await
2743 .expect("open failed");
2744 let mut i = 0;
2745 loop {
2746 let mut transaction = fs
2747 .root_store()
2748 .new_transaction(
2749 lock_keys![LockKey::object(
2750 root_store.store_object_id(),
2751 root_store.root_directory_object_id(),
2752 )],
2753 Options::default(),
2754 )
2755 .await
2756 .expect("new_transaction failed");
2757 root_directory
2758 .create_child_file(&mut transaction, &format!("trigger{i}"))
2759 .await
2760 .expect("create_child_file failed");
2761 transaction.commit().await.expect("commit failed");
2762
2763 if fs.journal().inner.lock().compaction_running {
2764 break;
2765 }
2766 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2767 MonotonicDuration::from_millis(1),
2768 ))
2769 .await;
2770 i += 1;
2771 }
2772 }
2773
2774 let mut count = 0;
2777 for _ in 0..1000 {
2778 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2779 MonotonicDuration::from_millis(20),
2780 ))
2781 .await;
2782 if !fs.journal().inner.lock().compaction_running {
2783 break;
2784 }
2785 count += 1;
2786 }
2787 assert!(!fs.journal().inner.lock().compaction_running);
2788
2789 assert!(count > 200);
2792
2793 stop.store(true, Ordering::Relaxed);
2794 fs.close().await.expect("Close failed");
2795 });
2796 assert!(executor.run_until_stalled(&mut fut).is_ready());
2797 }
2798
2799 #[fuchsia::test]
2800 #[cfg(target_os = "fuchsia")]
2801 fn test_low_priority_compaction_no_yielding_when_full() {
2802 let mut executor = TestExecutor::new_with_fake_time();
2803 let mut fut = std::pin::pin!(async {
2804 use std::sync::Arc;
2805 use std::sync::atomic::{AtomicBool, Ordering};
2806
2807 let reclaim_size = 65536;
2808 let device = DeviceHolder::new(FakeDevice::new(216384, TEST_DEVICE_BLOCK_SIZE));
2809 let fs = FxFilesystemBuilder::new()
2810 .journal_options(super::JournalOptions { reclaim_size, ..Default::default() })
2811 .format(true)
2812 .open(device)
2813 .await
2814 .expect("open failed");
2815
2816 let _low = fasync::LowPriorityTask::new();
2817
2818 {
2820 let root_store = fs.root_store();
2821 let root_directory =
2822 Directory::open(&root_store, root_store.root_directory_object_id())
2823 .await
2824 .expect("open failed");
2825 for i in 0..10 {
2826 let mut transaction = fs
2827 .root_store()
2828 .new_transaction(
2829 lock_keys![LockKey::object(
2830 root_store.store_object_id(),
2831 root_store.root_directory_object_id(),
2832 )],
2833 Options::default(),
2834 )
2835 .await
2836 .expect("new_transaction failed");
2837 root_directory
2838 .create_child_file(&mut transaction, &format!("test{}", i))
2839 .await
2840 .expect("create_child_file failed");
2841 transaction.commit().await.expect("commit failed");
2842 }
2843 }
2844
2845 let stop = Arc::new(AtomicBool::new(false));
2847 let stop_clone = stop.clone();
2848 let _normal_task = fasync::Task::spawn(async move {
2849 while !stop_clone.load(Ordering::Relaxed) {
2850 fasync::Timer::new(fasync::MonotonicInstant::after(
2851 MonotonicDuration::from_millis(3),
2852 ))
2853 .await;
2854 }
2855 });
2856
2857 {
2859 let root_store = fs.root_store();
2860 let root_directory =
2861 Directory::open(&root_store, root_store.root_directory_object_id())
2862 .await
2863 .expect("open failed");
2864 let mut i = 0;
2865 loop {
2866 let mut transaction = fs
2867 .root_store()
2868 .new_transaction(
2869 lock_keys![LockKey::object(
2870 root_store.store_object_id(),
2871 root_store.root_directory_object_id(),
2872 )],
2873 Options::default(),
2874 )
2875 .await
2876 .expect("new_transaction failed");
2877 root_directory
2878 .create_child_file(&mut transaction, &format!("trigger{i}"))
2879 .await
2880 .expect("create_child_file failed");
2881 transaction.commit().await.expect("commit failed");
2882
2883 let outstanding = {
2884 let inner = fs.journal().inner.lock();
2885 fs.journal().objects.last_end_offset()
2886 - inner.super_block_header.journal_checkpoint.file_offset
2887 };
2888 if outstanding >= reclaim_size * 7 / 8 {
2889 break;
2890 }
2891 i += 1;
2893 }
2894 }
2895
2896 TestExecutor::advance_to(fasync::MonotonicInstant::after(
2898 MonotonicDuration::from_millis(4),
2899 ))
2900 .await;
2901
2902 let _ = TestExecutor::poll_until_stalled(std::future::pending::<()>()).await;
2904 assert!(!fs.journal().inner.lock().compaction_running);
2905
2906 stop.store(true, Ordering::Relaxed);
2907 fs.close().await.expect("Close failed");
2908 });
2909 assert!(executor.run_until_stalled(&mut fut).is_ready());
2910 }
2911}
2912
2913#[cfg(fuzz)]
2914mod fuzz {
2915 use fuzz::fuzz;
2916
2917 #[fuzz]
2918 fn fuzz_journal_bytes(input: Vec<u8>) {
2919 use crate::filesystem::FxFilesystem;
2920 use fuchsia_async as fasync;
2921 use std::io::Write;
2922 use storage_device::DeviceHolder;
2923 use storage_device::fake_device::FakeDevice;
2924
2925 fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
2926 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
2927 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2928 fs.journal().inner.lock().writer.write_all(&input).expect("write failed");
2929 fs.close().await.expect("close failed");
2930 let device = fs.take_device().await;
2931 device.reopen(false);
2932 if let Ok(fs) = FxFilesystem::open(device).await {
2933 let _ = fs.close().await;
2936 }
2937 });
2938 }
2939
2940 #[fuzz]
2941 fn fuzz_journal(input: Vec<super::JournalRecord>) {
2942 use crate::filesystem::FxFilesystem;
2943 use fuchsia_async as fasync;
2944 use storage_device::DeviceHolder;
2945 use storage_device::fake_device::FakeDevice;
2946
2947 fasync::SendExecutorBuilder::new().num_threads(4).build().run(async move {
2948 let device = DeviceHolder::new(FakeDevice::new(32768, 512));
2949 let fs = FxFilesystem::new_empty(device).await.expect("new_empty failed");
2950 {
2951 let mut inner = fs.journal().inner.lock();
2952 for record in &input {
2953 let _ = inner.writer.write_record(record);
2954 }
2955 }
2956 fs.close().await.expect("close failed");
2957 let device = fs.take_device().await;
2958 device.reopen(false);
2959 if let Ok(fs) = FxFilesystem::open(device).await {
2960 let _ = fs.close().await;
2963 }
2964 });
2965 }
2966}