From aa2db56d089d10d4bbd566e3c9f8a5f2dcdee284 Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Fri, 8 May 2026 13:07:20 +0530 Subject: [PATCH 1/5] tools/cephfs: flush every journal event as soon as it is scanned NOTE: exclusive to only `event recover_dentries` JournalScanner::scan() runs a loop and loads the entire decoded journal in an `EventMap` called `events`. This can severely shoot up the RSS if the journal is huge (unflushed journal) making the tool less efficient. The way to curb the memory bomb is to scan and flush journal in batches. The flush boundary is based on the batch size and at event EVENT_SUBTREEMAP or EVENT_SUBTREEMAP_TEST(subtree event(s) mark a clear boundary). The knob to control this setting is via providing `--max-rss ` with the journal tool command. This patch divides the provided limit by three to create the batch since it is observed that the decoded journal events weight on an avg ~3x the encoded stream. Also: Currently, the events map retains all decoded events until all dentries are recovered, causing unnecessary memory retention. Fix this by erasing each event from the map right after it is processed. While the primary RSS bloat is caused by dnbl holding the encoded stream in memory, this incremental cleanup provides a minor optimization until a permanent fix is implemented (https://tracker.ceph.com/issues/77909). Partially-Fixes: https://tracker.ceph.com/issues/76422 Co-authored-by: Patrick Donnelly Signed-off-by: Dhairya Parmar --- src/tools/cephfs/JournalScanner.cc | 32 ++++++--- src/tools/cephfs/JournalScanner.h | 33 +++++---- src/tools/cephfs/JournalTool.cc | 109 ++++++++++++++++++++--------- 3 files changed, 115 insertions(+), 59 deletions(-) diff --git a/src/tools/cephfs/JournalScanner.cc b/src/tools/cephfs/JournalScanner.cc index 789dd54f844..03d37b77918 100644 --- a/src/tools/cephfs/JournalScanner.cc +++ b/src/tools/cephfs/JournalScanner.cc @@ -31,7 +31,7 @@ * that we were able to apply our checks, it does *not* mean that the journal is * healthy. */ -int JournalScanner::scan(bool const full) +int JournalScanner::scan(bool const full, EventCallback cb) { int r = 0; @@ -48,7 +48,7 @@ int JournalScanner::scan(bool const full) } if (full && header_present) { - r = scan_events(); + r = scan_events(cb); if (r < 0) { return r; } @@ -151,7 +151,7 @@ int JournalScanner::scan_header() } -int JournalScanner::scan_events() +int JournalScanner::scan_events(EventCallback cb) { uint64_t object_size = g_conf()->mds_log_segment_size; if (object_size == 0) { @@ -208,7 +208,7 @@ int JournalScanner::scan_events() } else { dout(4) << "Read 0x" << std::hex << this_object.length() << std::dec << " bytes from " << oid << " gap=" << gap << dendl; - objects_valid.push_back(oid); + ++num_objects_valid; this_object.begin().copy(this_object.length(), read_buf); } @@ -302,7 +302,12 @@ int JournalScanner::scan_events() } if (filter.apply(read_offset, *le)) { - events.insert_or_assign(read_offset, EventRecord(std::move(le), consumed)); + EventRecord er(std::move(le), consumed); + if (cb) { + cb(read_offset, er); + } else { + events.insert_or_assign(read_offset, std::move(er)); + } } } else { valid_entry = false; @@ -313,7 +318,12 @@ int JournalScanner::scan_events() auto q = le_bl.cbegin(); pi->decode(q); if (filter.apply(read_offset, *pi)) { - events.insert_or_assign(read_offset, EventRecord(std::move(pi), consumed)); + EventRecord er(std::move(pi), consumed); + if (cb) { + cb(read_offset, er); + } else { + events.insert_or_assign(read_offset, std::move(er)); + } } } catch (const buffer::error &err) { valid_entry = false; @@ -328,7 +338,7 @@ int JournalScanner::scan_events() read_offset += consumed; break; } else { - events_valid.push_back(read_offset); + ++num_events_valid; read_offset += consumed; } } @@ -340,10 +350,12 @@ int JournalScanner::scan_events() ranges_invalid.push_back(Range(gap_start, -1)); } - dout(4) << "Scanned objects, " << objects_missing.size() << " missing, " << objects_valid.size() << " valid" << dendl; + dout(4) << "Scanned objects, " << objects_missing.size() << " missing, " << num_objects_valid << " valid" << dendl; dout(4) << "Events scanned, " << ranges_invalid.size() << " gaps" << dendl; - dout(4) << "Found " << events_valid.size() << " valid events" << dendl; - dout(4) << "Selected " << events.size() << " events events for processing" << dendl; + dout(4) << "Found " << num_events_valid << " valid events" << dendl; + if (!cb) { + dout(4) << "Selected " << events.size() << " events for processing" << dendl; + } return 0; } diff --git a/src/tools/cephfs/JournalScanner.h b/src/tools/cephfs/JournalScanner.h index be2d27d039f..abe05c1d82d 100644 --- a/src/tools/cephfs/JournalScanner.h +++ b/src/tools/cephfs/JournalScanner.h @@ -15,6 +15,8 @@ #ifndef JOURNAL_SCANNER_H #define JOURNAL_SCANNER_H +#include + #include "include/rados/librados_fwd.hpp" // For Journaler::Header, can't forward-declare nested classes @@ -74,18 +76,6 @@ class JournalScanner ~JournalScanner(); - int set_journal_ino(); - int scan(bool const full=true); - int scan_pointer(); - int scan_header(); - int scan_events(); - void report(std::ostream &out) const; - - std::string obj_name(uint64_t offset) const; - std::string obj_name(inodeno_t ino, uint64_t offset) const; - - // The results of the scan - inodeno_t ino; // Corresponds to journal ino according their type struct EventRecord { EventRecord(std::unique_ptr le, uint32_t rs) : log_event(std::move(le)), raw_size(rs) {} EventRecord(std::unique_ptr p, uint32_t rs) : pi(std::move(p)), raw_size(rs) {} @@ -94,6 +84,21 @@ class JournalScanner uint32_t raw_size = 0; //< Size from start offset including all encoding overhead }; + using EventCallback = std::function; + + int set_journal_ino(); + int scan(bool const full=true, EventCallback cb = nullptr); + int scan_pointer(); + int scan_header(); + int scan_events(EventCallback cb = nullptr); + void report(std::ostream &out) const; + + std::string obj_name(uint64_t offset) const; + std::string obj_name(inodeno_t ino, uint64_t offset) const; + + // The results of the scan + inodeno_t ino; // Corresponds to journal ino according their type + class EventError { public: int r; @@ -114,10 +119,10 @@ class JournalScanner bool is_healthy() const; bool is_readable() const; - std::vector objects_valid; std::vector objects_missing; std::vector ranges_invalid; - std::vector events_valid; + uint64_t num_objects_valid{0}; + uint64_t num_events_valid{0}; EventMap events; // For events present in ::events (i.e. scanned successfully), diff --git a/src/tools/cephfs/JournalTool.cc b/src/tools/cephfs/JournalTool.cc index 9d99b5f5fec..20e14f2b159 100644 --- a/src/tools/cephfs/JournalTool.cc +++ b/src/tools/cephfs/JournalTool.cc @@ -72,7 +72,9 @@ void JournalTool::usage() << "\n" << "Special options\n" << " --alternate-pool Alternative metadata pool to target\n" - << " when using recover_dentries.\n"; + << " when using recover_dentries.\n" + << " --max-rss Maximum RSS allowed per batch during\n" + << " event recover_dentries.\n"; generic_client_usage(); } @@ -454,6 +456,9 @@ int JournalTool::main_event(std::vector &argv) } std::string output_path = "dump"; + uint64_t batch_size = 0; + uint64_t batch_bytes = 0; + JournalScanner::EventCallback flush = nullptr; while(arg != argv.end()) { std::string arg_str; if (ceph_argparse_witharg(argv, arg, &arg_str, "--path", (char*)NULL)) { @@ -464,6 +469,16 @@ int JournalTool::main_event(std::vector &argv) int r = rados.ioctx_create(arg_str.c_str(), output); ceph_assert(r == 0); other_pool = true; + } else if (ceph_argparse_witharg(argv, arg, &arg_str, "--max-rss", + nullptr)) { + std::string parse_err; + /* an estimate based on calculating journal event sizes, a decoded event + holds ~3x its encoded stream */ + batch_size = strict_strtoll(arg_str.c_str(), 0, &parse_err) / 3; + if (!parse_err.empty()) { + derr << "Invalid --max-rss value '" << arg_str << "': " << parse_err << dendl; + return -EINVAL; + } } else { cerr << "Unknown argument: '" << *arg << "'" << std::endl; return -EINVAL; @@ -482,45 +497,69 @@ int JournalTool::main_event(std::vector &argv) return r; } } else if (command == "recover_dentries") { - r = js.scan(); - if (r) { + std::set consumed_inos; + progress_tracker->set_operation_name("Processing events"); + + // decode the journal first to check if the header is valid + r = js.scan(false); + if (r < 0) { derr << "Failed to scan journal (" << cpp_strerror(r) << ")" << dendl; return r; } - - /** - * Iterate over log entries, attempting to scavenge from each one - */ - std::set consumed_inos; - uint64_t event_count = js.events.size(); - progress_tracker->set_operation_name("Processing events"); - progress_tracker->start(event_count); - - - for (JournalScanner::EventMap::iterator i = js.events.begin(); - i != js.events.end(); ++i) { - auto& le = i->second.log_event; - EMetaBlob const *mb = le->get_metablob(); - if (mb) { - int scav_r = recover_dentries(*mb, dry_run, &consumed_inos); - if (scav_r) { - dout(1) << "Error processing event 0x" << std::hex << i->first << std::dec - << ": " << cpp_strerror(scav_r) << ", continuing..." << dendl; - if (r == 0) { - r = scav_r; - } - // Our goal is to read all we can, so don't stop on errors, but - // do record them for possible later output - js.errors.insert(std::make_pair(i->first, - JournalScanner::EventError(scav_r, cpp_strerror(r)))); - } - } - - progress_tracker->increment(); - progress_tracker->display_progress(); + if (!js.header_present || !js.header_valid) { + derr << "Journal header is not present or is damaged, cannot recover dentries" << dendl; + return -EIO; } - progress_tracker->display_final_summary(); + // start the progress tracker with the diff b/w write and expire position + progress_tracker->start(js.header->write_pos - js.header->expire_pos); + + auto flush_events = [&]() { + for (auto it = js.events.begin(); it != js.events.end(); ) { + const auto& le = it->second.log_event; + EMetaBlob const *mb = le->get_metablob(); + if (mb) { + int scav_r = recover_dentries(*mb, dry_run, &consumed_inos); + if (scav_r) { + dout(1) << "Error processing event 0x" << std::hex << it->first << std::dec + << ": " << cpp_strerror(scav_r) << ", continuing..." << dendl; + js.errors.insert(std::make_pair(it->first, + JournalScanner::EventError(scav_r, cpp_strerror(scav_r)))); + } + } + it = js.events.erase(it); + progress_tracker->increment(); + progress_tracker->display_progress(); + } + batch_bytes = 0; + }; + + if (batch_size) { + std::cout << "Batch size: " << batch_size << " bytes" << std::endl; + + flush = [&](uint64_t offset, JournalScanner::EventRecord& er) { + batch_bytes += er.raw_size; + bool flushable = er.log_event + // factor in mds_debug_subtrees conf + && (er.log_event->get_type() == EVENT_SUBTREEMAP || er.log_event->get_type() == EVENT_SUBTREEMAP_TEST) + && batch_bytes >= batch_size; + js.events.insert_or_assign(offset, std::move(er)); + if (flushable) { + flush_events(); + } + }; + } + + r = js.scan_events(flush); + + flush_events(); + + if (r) { + derr << "Failed to scan events (" << cpp_strerror(r) << ")" << dendl; + return r; + } + + progress_tracker->display_final_summary(); /** From fc3ca7725bc9490f1fcdf55e41123cfbe607d118 Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Tue, 12 May 2026 01:06:04 +0530 Subject: [PATCH 2/5] qa: test cephfs-journal-recovery event recover_dentries summary with capping the maximum RSS to 500 MiB Partially-Fixes: https://tracker.ceph.com/issues/76422 Signed-off-by: Dhairya Parmar --- qa/tasks/cephfs/test_journal_repair.py | 101 +++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/qa/tasks/cephfs/test_journal_repair.py b/qa/tasks/cephfs/test_journal_repair.py index ae8548839c0..08fbf564204 100644 --- a/qa/tasks/cephfs/test_journal_repair.py +++ b/qa/tasks/cephfs/test_journal_repair.py @@ -208,6 +208,107 @@ class TestJournalRepair(CephFSTestCase): # Validate that the journal flush has trimmed the old journal objects self.assertGreater(len(journal_objs_before_reset), len(journal_objs_after_reset)) + def test_cephfs_journal_tool_recover_dentries_with_huge_journal(self): + """ + That after having a pile of unflushed dentries and mds crashed, + invocation of `cephfs-journal-tool recover_dentries summary` can + flush dentries into RADOS without getting consuming too much RSS + or getting OOM killed. + + NOTE: this test runs through 10M iterations each with 6 ops, totalling + to 60K ops. Please run this with caution. + """ + + # Register cleanup of the config_set so that a partial run can still + # reset the state + self.addCleanup(self.config_rm, 'mds', 'debug_mds') + self.addCleanup(self.config_rm, 'mds', 'debug_ms') + self.addCleanup(self.config_rm, 'mds', 'mds_cache_memory_limit') + self.addCleanup(self.config_rm, 'mds', 'mds_log_max_segments') + self.addCleanup(self.config_rm, 'mds', 'mds_log_warn_factor') + self.addCleanup(self.config_rm, 'mds', 'mds_log_trim_upkeep_interval') + self.addCleanup(self.config_rm, 'mds', 'mds_verify_scatter') + self.addCleanup(self.config_rm, 'mds', 'mds_debug_scatterstat') + + # debugging can be too slow given how long the file names are being + # used in the test case therefore turn off the mds + self.config_set('mds', 'debug_mds', '0') + self.config_set('mds', 'debug_ms', '0') + + # We do not want any unintended failover + self.fs.set_joinable(False) + + # Set the MDS cache limit to 32GiB + self.config_set('mds', 'mds_cache_memory_limit', '34359738368') + + # 100K segments limit is more than enough for 60M metadata ops + # with mds_log_events_per_segment being 1024 (default) + self.config_set('mds', 'mds_log_max_segments', '100000') + + # eases generating heath warnings + self.config_set('mds', 'mds_log_warn_factor', '1') + + # Sleep trim() for a day + self.config_set('mds', 'mds_log_trim_upkeep_interval', '86400') + + # Clutter the journal + self.mount_a.run_shell_payload( + """ +set -u +rm -rf w* +A200=$(printf 'A%.0s' {1..200}) +X400=$(printf 'X%.0s' {1..400}) +export A200 X400 +for w in $(seq 0 19); do +( + d="w${w}" + mkdir -p "$d/a" "$d/b" "$d/c" + for ((i = 0; i < 10000; i++)); do + touch "$d/a/${A200}_f${i}" + ln "$d/a/${A200}_f${i}" "$d/b/${A200}_l${i}" + mv "$d/b/${A200}_l${i}" "$d/c/${A200}_r${i}" + ln "$d/a/${A200}_f${i}" "$d/c/${A200}_h${i}" + mv "$d/a/${A200}_f${i}" "$d/b/${A200}_m${i}" + setfattr -n user.chaos -v "$X400" "$d/b/${A200}_m${i}" + done +) & +done +wait +""",timeout=43200) + + # kill the mds + self.fs.rank_fail(rank=0) + + # Try to access files from the client + blocked_ls = self.mount_a.run_shell(["ls", "w0/a"], wait=False) + log.info("Sleeping to check ls is blocked...") + time.sleep(60) + self.assertFalse(blocked_ls.finished) + self.mount_a.kill() + self.mount_a.kill_cleanup() + + # flush dentries into RADOS + self.fs.fail() + result = self.fs.journal_tool(["event", "recover_dentries", "summary", + "--max-rss", "524288000"], 0, + quiet=True) + log.info(f"recover_dentries result:\n{result}") + + # test dentries by truncating the journal + self.fs.journal_tool(['journal', 'reset', '--yes-i-really-really-mean-it'], 0) + + # Dir stats maybe inconsistent post journal reset + self.config_set('mds', 'mds_verify_scatter', 'false') + self.config_set('mds', 'mds_debug_scatterstat', 'false') + + self.fs.set_joinable(True) + status = self.fs.wait_for_daemons() + self.assertEqual(len(list(self.fs.get_ranks(status=status))), 1) + self.mount_a.mount_wait() + # checking one of the dirs should be enough + self.mount_a.run_shell(["ls", "w0"], wait=True) + + @for_teuthology # 308s def test_reset(self): """ From d611da1c2513e94e6718ba01e6701db321b33973 Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Mon, 15 Jun 2026 18:14:16 +0530 Subject: [PATCH 3/5] tools/cephfs: document --max-rss usage in first-damage.py Partially-Fixes: https://tracker.ceph.com/issues/76422 Signed-off-by: Dhairya Parmar --- src/tools/cephfs/first-damage.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tools/cephfs/first-damage.py b/src/tools/cephfs/first-damage.py index 763ab5af7f6..02483bba507 100644 --- a/src/tools/cephfs/first-damage.py +++ b/src/tools/cephfs/first-damage.py @@ -23,6 +23,16 @@ # # cephfs-journal-tool --rank=:0 event recover_dentries summary # +# NOTE: Large journal sizes can cause the tool's Resident Set Size (RSS) to +# spike significantly. To prevent excessive memory consumption, use the +# `--max-rss ` flag to cap the RSS. +# +# Avoid setting this value too low, as it will severely degrade runtime +# performance and prolong recovery. It is best practice to maintain a memory +# buffer of at least 30% of the tool's total budget. For example, if 100 GiB +# is to be allotted to the tool, cap the RSS at 70 GiB +# (`--max-rss 75161927680`). +# # 4b) If all good so far, reset the journal: # # cephfs-journal-tool --rank=:0 journal reset --yes-i-really-really-mean-it From 627af6cfc76acc7b3580f0ff7e6b0443b0ea9fff Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Mon, 15 Jun 2026 18:03:05 +0530 Subject: [PATCH 4/5] doc/cephfs: add note explainin the new batching technique Partially-Fixes: https://tracker.ceph.com/issues/76422 Signed-off-by: Dhairya Parmar --- doc/cephfs/cephfs-journal-tool.rst | 2 ++ doc/cephfs/disaster-recovery-experts.rst | 12 ++++++++++++ doc/cephfs/disaster-recovery.rst | 12 ++++++++++++ 3 files changed, 26 insertions(+) diff --git a/doc/cephfs/cephfs-journal-tool.rst b/doc/cephfs/cephfs-journal-tool.rst index d3e1cac48b0..dbd6ed03e96 100644 --- a/doc/cephfs/cephfs-journal-tool.rst +++ b/doc/cephfs/cephfs-journal-tool.rst @@ -150,6 +150,8 @@ Filtering: * ``--dname `` only include events referring to this named dentry within a directory fragment (may only be used in conjunction with ``--frag``) * ``--client `` only include events from this client session ID +* ``--max-rss `` (works only with ``recover_dentries`` for now) limits the RSS of the cephfs-journal-tool + by scanning and flushing journal events in batches Filters may be combined on an AND basis (i.e. only the intersection of events from each filter). diff --git a/doc/cephfs/disaster-recovery-experts.rst b/doc/cephfs/disaster-recovery-experts.rst index 19efe64ac36..c5a96d0f1f7 100644 --- a/doc/cephfs/disaster-recovery-experts.rst +++ b/doc/cephfs/disaster-recovery-experts.rst @@ -65,6 +65,18 @@ InoTables of each ``in`` MDS rank, to indicate that any written inodes' numbers are now in use. In simple cases, this will result in an entirely valid backing store state. +.. warning:: + + Large journal sizes can cause the tool's Resident Set Size (RSS) to spike + significantly. Use the ``--max-rss `` flag to limit memory usage, keeping the following + in mind: + + * **Performance Impact:** Do not set this value too low. Restricting memory excessively will + severely degrade runtime performance and prolong the recovery process. + * **30% Buffer Rule:** Maintain a safety buffer of at least 30% relative to the tool's total + memory budget. For example, if 100 GiB can be allotted to the tool, cap its RSS limit at + 70 GiB: ``--max-rss 75161927680``. + .. warning:: The resulting state of the backing store is not guaranteed to be diff --git a/doc/cephfs/disaster-recovery.rst b/doc/cephfs/disaster-recovery.rst index dd3b9d9c4aa..f7f9019d09a 100644 --- a/doc/cephfs/disaster-recovery.rst +++ b/doc/cephfs/disaster-recovery.rst @@ -85,6 +85,18 @@ Using first-damage.py cephfs-journal-tool --rank=:0 event recover_dentries summary + .. warning:: + + Large journal sizes can cause the tool's Resident Set Size (RSS) to + spike significantly. To prevent excessive memory consumption, use the + ``--max-rss `` flag to cap the RSS. + + Avoid setting this value too low, as it will severely degrade runtime + performance and prolong recovery. It is best practice to maintain a memory + buffer of at least 30% of the tool's total budget. For example, if 100 GiB + is to be allotted to the tool, cap the RSS at 70 GiB + (``--max-rss 75161927680``). + #. Reset the journal: .. prompt:: bash # From 0e9af4b99e04b5bd97b38ee3998a661014b26e9c Mon Sep 17 00:00:00 2001 From: Dhairya Parmar Date: Fri, 3 Jul 2026 16:05:06 +0530 Subject: [PATCH 5/5] tools/cephfs: make journal pointer/header failure explicit iIf the journal pointer and/or header is missing or corrupt, the tool silently returns with no-op. There is no dentry recovery happening if any of the objects are missing but it should be explicit to the user. This still doesn't change returning errnos on failures to maintain status quo until https://tracker.ceph.com/issues/77913 is addressed One important change: The condition to scan events is now changed from header_present to header_valid. The reason for this can be understood by reading JournalScanner::scan_header -- if the header could not be decoded, it is set to null, using this in JournalTool::recover_dentries would segfault and other corruptions include bad magic or inconsistent offsets. A recovery should not proceed with distorted magic/offset. Fixes: https://tracker.ceph.com/issues/76422 Signed-off-by: Dhairya Parmar --- src/tools/cephfs/JournalScanner.cc | 19 ++++++++++++++----- src/tools/cephfs/JournalTool.cc | 13 +++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/tools/cephfs/JournalScanner.cc b/src/tools/cephfs/JournalScanner.cc index 03d37b77918..8e011c82b35 100644 --- a/src/tools/cephfs/JournalScanner.cc +++ b/src/tools/cephfs/JournalScanner.cc @@ -40,17 +40,26 @@ int JournalScanner::scan(bool const full, EventCallback cb) return r; } - if (!is_mdlog || pointer_present) { + if (is_mdlog && !pointer_present) { + derr << "Aborting journal scan" << dendl; + return 0; + } else { r = scan_header(); if (r < 0) { return r; } } - if (full && header_present) { - r = scan_events(cb); - if (r < 0) { - return r; + // NOTE:: ensure the caller checks for header validity before scanning events + if (full) { + if (!header_valid) { + derr << "Aborting journal scan" << dendl; + return 0; + } else { + r = scan_events(cb); + if (r < 0) { + return r; + } } } diff --git a/src/tools/cephfs/JournalTool.cc b/src/tools/cephfs/JournalTool.cc index 20e14f2b159..9711adf3e0b 100644 --- a/src/tools/cephfs/JournalTool.cc +++ b/src/tools/cephfs/JournalTool.cc @@ -500,15 +500,20 @@ int JournalTool::main_event(std::vector &argv) std::set consumed_inos; progress_tracker->set_operation_name("Processing events"); - // decode the journal first to check if the header is valid + // decode the journal first to check if the journal pointer and header are valid r = js.scan(false); if (r < 0) { derr << "Failed to scan journal (" << cpp_strerror(r) << ")" << dendl; return r; } - if (!js.header_present || !js.header_valid) { - derr << "Journal header is not present or is damaged, cannot recover dentries" << dendl; - return -EIO; + if (!js.pointer_present) { + derr << "Cannot recover dentries: journal pointer is missing" << dendl; + return 0; + } + // if header_valid is false, header_present is also false + if (!js.header_valid) { + derr << "Cannot recover dentries: journal header is missing or invalid" << dendl; + return 0; } // start the progress tracker with the diff b/w write and expire position