# Ceph - scalable distributed file system # # Copyright (C) 2022 Red Hat, Inc. # # This is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License version 2.1, as published by the Free Software # Foundation. See file COPYING. # Suggested recovery sequence (for single MDS cluster): # # 1) Unmount all clients. # # 2) Flush the journal (if possible): # # ceph tell mds.:0 flush journal # # 3) Fail the file system: # # ceph fs fail # # 4a) Recover dentries from the journal. This will be a no-op if the MDS flushed the journal successfully: # # cephfs-journal-tool --rank=:0 event recover_dentries summary # # 4b) If all good so far, reset the journal: # # cephfs-journal-tool --rank=:0 journal reset # # 5) Run this tool to see list of damaged dentries: # # python3 first-damage.py --memo run.1 # # 6) Optionally, remove them: # # python3 first-damage.py --memo run.2 --remove # # Note: use --memo to specify a different file to save objects that have # already been traversed, for independent runs. # # This has the effect of removing that dentry from the snapshot or HEAD # (current hierarchy). Note: the inode's linkage will be lost. The inode may # be recoverable in lost+found during a future data scan recovery. import argparse import logging import os import rados import re import sys import struct log = logging.getLogger("first-damage-traverse") MEMO = None REMOVE = False POOL = None NEXT_SNAP = None CONF = os.environ.get('CEPH_CONF') REPAIR_NOSNAP = None CEPH_NOSNAP = 0xfffffffe # int32 -2 DIR_PATTERN = re.compile(r'[0-9a-fA-F]{8,}\.[0-9a-fA-F]+') CACHE = set() def traverse(MEMO, ioctx): for o in ioctx.list_objects(): if not DIR_PATTERN.fullmatch(o.key): log.debug("skipping %s", o.key) continue elif o.key in CACHE: log.debug("skipping previously examined object %s", o.key) continue log.info("examining: %s", o.key) with rados.ReadOpCtx() as rctx: nkey = None while True: it = ioctx.get_omap_vals(rctx, nkey, None, 100, omap_key_type=bytes)[0] ioctx.operate_read_op(rctx, o.key) nkey = None for (dnk, val) in it: log.debug(f'\t{dnk}: val size {len(val)}') (first,) = struct.unpack(' NEXT_SNAP: log.warning(f"found {o.key}:{dnk} first (0x{first:x}) > NEXT_SNAP (0x{NEXT_SNAP:x})") if REPAIR_NOSNAP and dnk.endswith(b"_head") and first == CEPH_NOSNAP: log.warning(f"repairing first==CEPH_NOSNAP damage, setting to NEXT_SNAP (0x{NEXT_SNAP:x})") first = NEXT_SNAP nval = bytearray(val) struct.pack_into("