diff options
-rw-r--r-- | fs/xfs/scrub/repair.c | 142 | ||||
-rw-r--r-- | fs/xfs/scrub/repair.h | 2 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.c | 11 | ||||
-rw-r--r-- | fs/xfs/scrub/scrub.h | 5 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.c | 3 | ||||
-rw-r--r-- | fs/xfs/xfs_inode.h | 1 | ||||
-rw-r--r-- | fs/xfs/xfs_xchgrange.c | 2 | ||||
-rw-r--r-- | fs/xfs/xfs_xchgrange.h | 3 |
8 files changed, 166 insertions, 3 deletions
diff --git a/fs/xfs/scrub/repair.c b/fs/xfs/scrub/repair.c index dc2f9091ff8c..20cab9607841 100644 --- a/fs/xfs/scrub/repair.c +++ b/fs/xfs/scrub/repair.c @@ -31,6 +31,10 @@ #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 "xfs_xchgrange.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/trace.h" @@ -1537,3 +1541,141 @@ 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); + bool use_atomic; + int error; + + if (!(sc->sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR)) + return 0; + if (XFS_FORCED_SHUTDOWN(mp)) + return -EIO; + + ASSERT(sc->tp == NULL); + ASSERT(sc->tempip == NULL); + + /* Enable atomic extent swapping. */ + error = xfs_swapext_enable_log_assist(mp, true, &use_atomic); + if (error) + return error; + ASSERT(use_atomic); + sc->flags |= XREP_ATOMIC_SWAPEXT; + + /* + * Make sure that we have allocated dquot(s) on disk. The temporary + * inode should be completely root owned so that we don't fail due to + * quota limits. + */ + error = xfs_qm_vop_dqalloc(sc->mp->m_rootip, GLOBAL_ROOT_UID, + GLOBAL_ROOT_GID, 0, XFS_QMOPT_QUOTALL, &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_icreate(mp, tres, udqp, gdqp, pdqp, resblks, + &tp); + if (error) + goto out_release_dquots; + + /* Allocate inode, set up directory. */ + error = xfs_dir_ialloc(&init_user_ns, &tp, sc->mp->m_rootip, mode, 0, + 0, 0, &sc->tempip); + if (error) + goto out_trans_cancel; + + /* Change the ownership of the inode to root. */ + VFS_I(sc->tempip)->i_uid = GLOBAL_ROOT_UID; + VFS_I(sc->tempip)->i_gid = GLOBAL_ROOT_GID; + xfs_trans_log_inode(tp, sc->tempip, XFS_ILOG_CORE); + + /* + * Mark our temporary file as private so that LSMs and the ACL code + * don't try to add their own metadata or reason about these files. + * The file should never be exposed to userspace. + */ + VFS_I(sc->tempip)->i_flags |= S_PRIVATE; + VFS_I(sc->tempip)->i_opflags &= ~IOP_XATTR; + + 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); + } +out_release_dquots: + 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 a6f45c63fc20..b0496e8555df 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; @@ -207,6 +208,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 2fcd3409bc9b..22da6fd48420 100644 --- a/fs/xfs/scrub/scrub.c +++ b/fs/xfs/scrub/scrub.c @@ -19,6 +19,7 @@ #include "xfs_scrub.h" #include "xfs_btree.h" #include "xfs_btree_staging.h" +#include "xfs_log.h" #include "scrub/scrub.h" #include "scrub/common.h" #include "scrub/trace.h" @@ -168,6 +169,10 @@ xchk_teardown( xfs_irele(sc->ip); sc->ip = NULL; } + if (sc->flags & XREP_ATOMIC_SWAPEXT) { + xlog_drop_incompat_feat(sc->mp->m_log); + sc->flags &= ~XREP_ATOMIC_SWAPEXT; + } if (sc->flags & XCHK_FS_FROZEN) { int err2 = xchk_fs_thaw(sc); @@ -193,6 +198,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 2e5ec273e858..9121faa9dbeb 100644 --- a/fs/xfs/scrub/scrub.h +++ b/fs/xfs/scrub/scrub.h @@ -86,6 +86,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; @@ -105,6 +109,7 @@ struct xfs_scrub { #define XCHK_HAS_QUOTAOFFLOCK (1 << 1) /* we hold the quotaoff lock */ #define XCHK_REAPING_DISABLED (1 << 2) /* background block reaping paused */ #define XCHK_FS_FROZEN (1 << 3) /* we froze the fs to do things */ +#define XREP_ATOMIC_SWAPEXT (1 << 29) /* uses atomic extent swapping */ #define XREP_RESET_PERAG_RESV (1 << 30) /* must reset AG space reservation */ #define XREP_ALREADY_FIXED (1 << 31) /* checking our repair work */ diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c index 59706de3a9d0..5eb60df62270 100644 --- a/fs/xfs/xfs_inode.c +++ b/fs/xfs/xfs_inode.c @@ -40,7 +40,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 *); /* @@ -2169,7 +2168,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 81c7c695fb92..b44bad05ef82 100644 --- a/fs/xfs/xfs_inode.h +++ b/fs/xfs/xfs_inode.h @@ -495,6 +495,7 @@ bool xfs_inode_needs_inactivation(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); diff --git a/fs/xfs/xfs_xchgrange.c b/fs/xfs/xfs_xchgrange.c index 395ab886ebe5..aecb935f8bc5 100644 --- a/fs/xfs/xfs_xchgrange.c +++ b/fs/xfs/xfs_xchgrange.c @@ -482,7 +482,7 @@ xfs_xchg_range_reserve_quota( * permission either (1) by calling xlog_drop_incompat_feat when they're done, * or (2) by setting XFS_TRANS_LOG_INCOMPAT on a transaction. */ -STATIC int +int xfs_swapext_enable_log_assist( struct xfs_mount *mp, bool force, diff --git a/fs/xfs/xfs_xchgrange.h b/fs/xfs/xfs_xchgrange.h index cca297034689..f3e411be6580 100644 --- a/fs/xfs/xfs_xchgrange.h +++ b/fs/xfs/xfs_xchgrange.h @@ -27,4 +27,7 @@ int xfs_xchg_range_prep(struct file *file1, struct file *file2, int xfs_xchg_range(struct xfs_inode *ip1, struct xfs_inode *ip2, const struct file_xchg_range *fxr, unsigned int private_flags); +int xfs_swapext_enable_log_assist(struct xfs_mount *mp, bool force, + bool *enabled); + #endif /* __XFS_XCHGRANGE_H__ */ |