diff options
Diffstat (limited to 'fs/xfs')
-rw-r--r-- | fs/xfs/scrub/repair.c | 127 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 6 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.h | 4 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.c | 3 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.h | 1 |
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); |