diff options
author | Darrick J. Wong <djwong@kernel.org> | 2021-09-01 11:25:37 -0700 |
---|---|---|
committer | Darrick J. Wong <djwong@kernel.org> | 2021-09-17 18:55:29 -0700 |
commit | 803efaa936528ec6a35e4e521cddeb440236c9d0 (patch) | |
tree | 25e7f83d5f098589f4d9d0f1fdc7affa67c9e6b8 | |
parent | e2ae66671e52f06e6dba1c0b4339897759c5e590 (diff) |
xfs: teach repair to fix file nlinksscrub-nlinks_2021-09-17
Fix the nlinks now too.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
-rw-r--r-- | fs/xfs/Makefile | 1 | ||||
-rw-r--r-- | fs/xfs/scrub/nlinks_repair.c | 200 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/trace.h | 21 |
5 files changed, 225 insertions, 1 deletions
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile index 17fb0e9e2e9c..4c4b7556e711 100644 --- a/fs/xfs/Makefile +++ b/fs/xfs/Makefile @@ -190,6 +190,7 @@ xfs-y += $(addprefix scrub/, \ fscounters_repair.o \ ialloc_repair.o \ inode_repair.o \ + nlinks_repair.o \ parent_repair.o \ refcount_repair.o \ repair.o \ diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c new file mode 100644 index 000000000000..c4527c1bfc7f --- /dev/null +++ b/fs/xfs/scrub/nlinks_repair.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Oracle. All Rights Reserved. + * Author: Darrick J. Wong <djwong@kernel.org> + */ +#include "xfs.h" +#include "xfs_fs.h" +#include "xfs_shared.h" +#include "xfs_format.h" +#include "xfs_trans_resv.h" +#include "xfs_mount.h" +#include "xfs_log_format.h" +#include "xfs_trans.h" +#include "xfs_inode.h" +#include "xfs_icache.h" +#include "xfs_bmap_util.h" +#include "xfs_iwalk.h" +#include "xfs_ialloc.h" +#include "xfs_sb.h" +#include "scrub/scrub.h" +#include "scrub/common.h" +#include "scrub/repair.h" +#include "scrub/array.h" +#include "scrub/iscan.h" +#include "scrub/nlinks.h" +#include "scrub/trace.h" + +/* + * Live Inode Link Count Repair + * ============================ + * + * Use the live inode link count information that we collected to replace the + * nlink values of the incore inodes. A scrub->repair cycle should have left + * the live data and hooks active, so this is safe so long as we make sure the + * inode is locked. + */ + +/* Update incore link count information. Caller must hold the xnc lock. */ +static int +xrep_nlinks_ino_set( + struct xchk_nlinks *xnc, + xfs_ino_t ino, + xfs_nlink_t nlinks) +{ + int error; + + trace_xrep_nlinks_ino_set(xnc->sc->mp, ino, nlinks); + + if (!xnc->nlinks) + return 0; + + error = xfbma_set(xnc->nlinks, ino, &nlinks); + if (error == -EFBIG) { + /* + * EFBIG means we tried to store data at too high a byte offset + * in the sparse array. This should be impossible since we + * presumably already stored an nlink count, but we still need + * to fail gracefully. + */ + return -ECANCELED; + } + + return error; +} + +/* Commit new counters to an inode. */ +static int +xrep_nlinks_commit_inode( + struct xfs_mount *mp, + struct xfs_trans *tp_unused, + xfs_ino_t ino, + void *data) +{ + struct xchk_nlinks *xnc = data; + struct xfs_inode *ip = NULL; + struct xfs_trans *tp; + xfs_nlink_t live_nlink; + int error; + + error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp); + if (error) + return error; + + error = xfs_iget(mp, tp, ino, XFS_IGET_UNTRUSTED, XFS_ILOCK_EXCL, &ip); + if (error == -ENOENT || error == -EINVAL) { + /* Inode wasn't found, so we'll check for zero nlink. */ + error = 0; + } + if (error) + goto out_cancel; + + xchk_iscan_lock(&xnc->iscan); + if (xnc->hook_dead) { + error = -ECANCELED; + goto out_unlock; + } + error = xchk_nlinks_get_shadow_count(xnc, ino, &live_nlink); + if (error) + goto out_unlock; + + if (ip == NULL) { + /* + * We couldn't get the inode, so the link count had better be + * zero! This means we cannot fix the filesystem. + */ + if (live_nlink != 0) { + trace_xrep_nlinks_unfixable_inode(mp, ino, 0, + live_nlink); + error = -EFSCORRUPTED; + } + goto out_unlock; + } + + /* + * We can't have directories with a live nlink count of 1. We grabbed + * the directory inode, which means that the ondisk inode has a nonzero + * nlink. Set the link counts to two. + */ + if (S_ISDIR(VFS_I(ip)->i_mode) && live_nlink == 1) { + live_nlink = 2; + error = xrep_nlinks_ino_set(xnc, ip->i_ino, live_nlink); + if (error) + goto out_unlock; + } + + if (live_nlink == VFS_I(ip)->i_nlink) + goto out_unlock; + + /* Commit the new link count, if necessary. */ + xchk_iscan_unlock(&xnc->iscan); + + trace_xrep_nlinks_commit_inode(mp, ino, VFS_I(ip)->i_nlink, + live_nlink); + + xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); + set_nlink(VFS_I(ip), live_nlink); + xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE); + error = xfs_trans_commit(tp); + xchk_irele(xnc->sc, ip); + return error; + +out_unlock: + xchk_iscan_unlock(&xnc->iscan); + if (ip) { + xfs_iunlock(ip, XFS_ILOCK_EXCL); + xchk_irele(xnc->sc, ip); + } +out_cancel: + xfs_trans_cancel(tp); + return error; +} + +/* Commit the new inode link counters. */ +int +xrep_nlinks( + struct xfs_scrub *sc) +{ + struct xchk_nlinks *xnc = sc->buf; + uint64_t nr = 0; + xfs_nlink_t nlink; + int error; + + /* Commit the scrub transaction so that we can use multithreaded iwalk. */ + error = xfs_trans_commit(sc->tp); + sc->tp = NULL; + if (error) + return error; + + /* Update the link count of every inode that the inobt knows about. */ + error = xfs_iwalk_threaded(sc->mp, 0, XFS_IWALK_METADIR, + xrep_nlinks_commit_inode, 0, false, xnc); + if (error) + return error; + + /* + * Make a second pass to find inodes for which we have positive link + * count but don't seem to exist on disk. We cannot resuscitate dead + * inodes, but we can at least signal failure. + */ + xchk_iscan_lock(&xnc->iscan); + while (!(error = xfbma_iter_get(xnc->nlinks, &nr, &nlink))) { + xchk_iscan_unlock(&xnc->iscan); + + if (xchk_should_terminate(xnc->sc, &error)) + return error; + + error = xrep_nlinks_commit_inode(sc->mp, NULL, nr - 1, xnc); + if (error) + return error; + + xchk_iscan_lock(&xnc->iscan); + } + xchk_iscan_unlock(&xnc->iscan); + + /* ENODATA means we hit the end of the array. */ + if (error == -ENODATA) + return 0; + + return error; +} diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h index b4a5043394a2..de0a219f059b 100644 --- a/fs/xfs/scrub/repair.h +++ b/fs/xfs/scrub/repair.h @@ -121,6 +121,7 @@ int xrep_fscounters(struct xfs_scrub *sc); int xrep_xattr(struct xfs_scrub *sc); int xrep_dir(struct xfs_scrub *sc); int xrep_parent(struct xfs_scrub *sc); +int xrep_nlinks(struct xfs_scrub *sc); #ifdef CONFIG_XFS_QUOTA int xrep_quota(struct xfs_scrub *sc); @@ -267,6 +268,7 @@ xrep_rmapbt_setup( #define xrep_rtbitmap xrep_notsupported #define xrep_rtrmapbt xrep_notsupported #define xrep_rtrefcountbt xrep_notsupported +#define xrep_nlinks xrep_notsupported #endif /* CONFIG_XFS_ONLINE_REPAIR */ diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c index 44890551f73e..d3944a1d0146 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -409,7 +409,7 @@ static const struct xchk_meta_ops meta_scrub_ops[] = { .type = ST_FS, .setup = xchk_setup_nlinks, .scrub = xchk_nlinks, - .repair = xrep_notsupported, + .repair = xrep_nlinks, }, }; diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h index 7892b3e1d140..732a7681d691 100644 --- a/fs/xfs/scrub/trace.h +++ b/fs/xfs/scrub/trace.h @@ -1912,6 +1912,27 @@ TRACE_EVENT(xrep_rtrefc_found, __entry->refcount) ) +DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_commit_inode); +DEFINE_SCRUB_NLINK_DIFF_EVENT(xrep_nlinks_unfixable_inode); + +TRACE_EVENT(xrep_nlinks_ino_set, + TP_PROTO(struct xfs_mount *mp, xfs_ino_t ino, xfs_nlink_t nlinks), + TP_ARGS(mp, ino, nlinks), + TP_STRUCT__entry( + __field(dev_t, dev) + __field(xfs_ino_t, ino) + __field(xfs_nlink_t, nlinks) + ), + TP_fast_assign( + __entry->dev = mp->m_super->s_dev; + __entry->ino = ino; + __entry->nlinks = nlinks; + ), + TP_printk("dev %d:%d ino 0x%llx nlinks %u", + MAJOR(__entry->dev), MINOR(__entry->dev), + __entry->ino, __entry->nlinks) +) + #endif /* IS_ENABLED(CONFIG_XFS_ONLINE_REPAIR) */ #endif /* _TRACE_XFS_SCRUB_TRACE_H */ |