summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDarrick J. Wong <darrick.wong@oracle.com>2020-10-25 17:15:48 -0700
committerDarrick J. Wong <darrick.wong@oracle.com>2020-10-26 18:32:24 -0700
commit32c6e10d243d14d2e20bd8471d761f12785e20f0 (patch)
treeba528b20a01d2286270a08d5ff178af34512d0c2
parent0f077c3a4efbe8964a8b7a7f6cc9b62767699f8d (diff)
xfs: create temporary files and directories for online repair
Teach the online repair code how to create temporary files or directories. These temporary files can be used to stage reconstructed information until we're ready to perform an atomic extent swap to commit the new metadata. Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
-rw-r--r--fs/xfs/scrub/repair.c127
-rw-r--r--fs/xfs/scrub/repair.h2
-rw-r--r--fs/xfs/scrub/scrub.c6
-rw-r--r--fs/xfs/scrub/scrub.h4
-rw-r--r--fs/xfs/xfs_inode.c3
-rw-r--r--fs/xfs/xfs_inode.h1
6 files changed, 141 insertions, 2 deletions
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c
index c27040208196..222ab8746513 100644
--- a/fs/xfs/scrub/repair.c
+++ b/fs/xfs/scrub/repair.c
@@ -31,6 +31,9 @@
#include "xfs_extfree_item.h"
#include "xfs_reflink.h"
#include "xfs_health.h"
+#include "xfs_bmap_btree.h"
+#include "xfs_trans_space.h"
+#include "xfs_dir2.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
#include "scrub/trace.h"
@@ -1491,3 +1494,127 @@ out:
sc->sm->sm_flags = smflags;
return error;
}
+
+/*
+ * Create a temporary file for reconstructing metadata, with the intention of
+ * atomically swapping the temporary file's contents with the file that's
+ * being repaired.
+ */
+int
+xrep_setup_tempfile(
+ struct xfs_scrub *sc,
+ uint16_t mode)
+{
+ struct xfs_mount *mp = sc->mp;
+ struct xfs_trans *tp = NULL;
+ struct xfs_dquot *udqp = NULL;
+ struct xfs_dquot *gdqp = NULL;
+ struct xfs_dquot *pdqp = NULL;
+ struct xfs_trans_res *tres;
+ unsigned int resblks;
+ bool is_dir = S_ISDIR(mode);
+ int error;
+
+ if (!(sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR))
+ return 0;
+ if (!xfs_sb_version_hasatomicswap(&sc->mp->m_sb))
+ return -EOPNOTSUPP;
+ if (XFS_FORCED_SHUTDOWN(mp))
+ return -EIO;
+
+ ASSERT(sc->tp == NULL);
+ ASSERT(sc->tempip == NULL);
+
+ /*
+ * Make sure that we have allocated dquot(s) on disk. The temporary
+ * inode should be completely root owned, but we'll still go through
+ * the motions to keep the quota accounting accurate.
+ */
+ error = xfs_qm_vop_dqalloc(sc->mp->m_rootip, current_fsuid(),
+ current_fsgid(), 0,
+ XFS_QMOPT_QUOTALL | XFS_QMOPT_INHERIT,
+ &udqp, &gdqp, &pdqp);
+ if (error)
+ return error;
+
+ if (is_dir) {
+ resblks = XFS_MKDIR_SPACE_RES(mp, 0);
+ tres = &M_RES(mp)->tr_mkdir;
+ } else {
+ resblks = XFS_IALLOC_SPACE_RES(mp);
+ tres = &M_RES(mp)->tr_create_tmpfile;
+ }
+
+ error = xfs_trans_alloc(mp, tres, resblks, 0, 0, &tp);
+ if (error)
+ goto out_release_inode;
+
+ error = xfs_trans_reserve_quota(tp, mp, udqp, gdqp, pdqp, resblks,
+ 1, 0);
+ if (error)
+ goto out_trans_cancel;
+
+ /* Allocate inode, set up directory. */
+ error = xfs_dir_ialloc(&tp, sc->mp->m_rootip, mode, 0, 0, 0,
+ &sc->tempip);
+ if (error)
+ goto out_trans_cancel;
+
+ if (is_dir) {
+ error = xfs_dir_init(tp, sc->tempip, sc->mp->m_rootip);
+ if (error)
+ goto out_trans_cancel;
+ }
+
+ /*
+ * Attach the dquot(s) to the inodes and modify them incore.
+ * These ids of the inode couldn't have changed since the new
+ * inode has been locked ever since it was created.
+ */
+ xfs_qm_vop_create_dqattach(tp, sc->tempip, udqp, gdqp, pdqp);
+
+ /*
+ * Put our temp file on the unlinked list so it's purged automatically.
+ * Anything being reconstructed using this file must be atomically
+ * swapped with the original file because the contents here will be
+ * purged when the inode is dropped or log recovery cleans out the
+ * unlinked list.
+ */
+ error = xfs_iunlink(tp, sc->tempip);
+ if (error)
+ goto out_trans_cancel;
+
+ error = xfs_trans_commit(tp);
+ if (error)
+ goto out_release_inode;
+
+ xfs_qm_dqrele(udqp);
+ xfs_qm_dqrele(gdqp);
+ xfs_qm_dqrele(pdqp);
+
+ /* Finish setting up the incore / vfs context. */
+ xfs_setup_iops(sc->tempip);
+ xfs_finish_inode_setup(sc->tempip);
+
+ sc->temp_ilock_flags = 0;
+ return error;
+
+out_trans_cancel:
+ xfs_trans_cancel(tp);
+out_release_inode:
+ /*
+ * Wait until after the current transaction is aborted to finish the
+ * setup of the inode and release the inode. This prevents recursive
+ * transactions and deadlocks from xfs_inactive.
+ */
+ if (sc->tempip) {
+ xfs_finish_inode_setup(sc->tempip);
+ xfs_irele(sc->tempip);
+ }
+
+ xfs_qm_dqrele(udqp);
+ xfs_qm_dqrele(gdqp);
+ xfs_qm_dqrele(pdqp);
+
+ return error;
+}
diff --git a/fs/xfs/scrub/repair.h b/fs/xfs/scrub/repair.h
index 7747ba9e5902..98399ebf7f08 100644
--- a/fs/xfs/scrub/repair.h
+++ b/fs/xfs/scrub/repair.h
@@ -35,6 +35,7 @@ int xrep_alloc_ag_block(struct xfs_scrub *sc,
int xrep_init_btblock(struct xfs_scrub *sc, xfs_fsblock_t fsb,
struct xfs_buf **bpp, xfs_btnum_t btnum,
const struct xfs_buf_ops *ops);
+int xrep_setup_tempfile(struct xfs_scrub *sc, uint16_t mode);
struct xbitmap;
@@ -188,6 +189,7 @@ xrep_rmapbt_setup(
return xchk_setup_ag_btree(sc, ip, false);
}
+#define xrep_setup_tempfile(sc, mode) (0)
#define xrep_revalidate_allocbt (NULL)
#define xrep_revalidate_iallocbt (NULL)
diff --git a/fs/xfs/scrub/scrub.c b/fs/xfs/scrub/scrub.c
index 7ea166edde0f..fa46a9958c9f 100644
--- a/fs/xfs/scrub/scrub.c
+++ b/fs/xfs/scrub/scrub.c
@@ -197,6 +197,12 @@ xchk_teardown(
sc->buf_cleanup = NULL;
sc->buf = NULL;
}
+ if (sc->tempip) {
+ if (sc->temp_ilock_flags)
+ xfs_iunlock(sc->tempip, sc->temp_ilock_flags);
+ xfs_irele(sc->tempip);
+ sc->tempip = NULL;
+ }
return error;
}
diff --git a/fs/xfs/scrub/scrub.h b/fs/xfs/scrub/scrub.h
index e89682ea18d5..2364e36e8545 100644
--- a/fs/xfs/scrub/scrub.h
+++ b/fs/xfs/scrub/scrub.h
@@ -84,6 +84,10 @@ struct xfs_scrub {
/* Lock flags for @ip. */
uint ilock_flags;
+ /* A temporary file on this filesystem, for staging new metadata. */
+ struct xfs_inode *tempip;
+ uint temp_ilock_flags;
+
/* See the XCHK/XREP state flags below. */
unsigned int flags;
diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index b5479881a334..6736f16d92a5 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -42,7 +42,6 @@
kmem_zone_t *xfs_inode_zone;
-STATIC int xfs_iunlink(struct xfs_trans *, struct xfs_inode *);
STATIC int xfs_iunlink_remove(struct xfs_trans *, struct xfs_inode *);
/*
@@ -2455,7 +2454,7 @@ out:
* We place the on-disk inode on a list in the AGI. It will be pulled from this
* list when the inode is freed.
*/
-STATIC int
+int
xfs_iunlink(
struct xfs_trans *tp,
struct xfs_inode *ip)
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 5b9dd68596ec..cdb8f5e4d276 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -486,6 +486,7 @@ void xfs_inode_inactivation_cleanup(struct xfs_inode *ip);
int xfs_iunlink_init(struct xfs_perag *pag);
void xfs_iunlink_destroy(struct xfs_perag *pag);
+int xfs_iunlink(struct xfs_trans *tp, struct xfs_inode *ip);
void xfs_end_io(struct work_struct *work);