xfs: launder in-memory btree buffers before transaction commit

As we've noted in various places, all current users of in-memory btrees
are online fsck.  Online fsck only stages a btree long enough to rebuild
an ondisk data structure, which means that the in-memory btree is
ephemeral.  Furthermore, if we encounter /any/ errors while updating an
in-memory btree, all we do is tear down all the staged data and return
an errno to userspace.  In-memory btrees need not be transactional, so
their buffers should not be committed to the ondisk log, nor should they
be checkpointed by the AIL.  That's just as well since the ephemeral
nature of the btree means that the buftarg and the buffers may disappear
quickly anyway.

Therefore, we need a way to launder the btree buffers that get attached
to the transaction by the generic btree code.  Because the buffers are
directly mapped to backing file pages, there's no need to bwrite them
back to the tmpfs file.  All we need to do is clean enough of the buffer
log item state so that the bli can be detached from the buffer, remove
the bli from the transaction's log item list, and reset the transaction
dirty state as if the laundered items had never been there.

For simplicity, create xfbtree transaction commit and cancel helpers
that launder the in-memory btree buffers for callers.  Once laundered,
call the write verifier on non-stale buffers to avoid integrity issues,
or punch a hole in the backing file for stale buffers.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Darrick J. Wong 2024-02-22 12:43:36 -08:00
parent a095686a23
commit 0dc63c8a1c
7 changed files with 236 additions and 0 deletions

View File

@ -226,3 +226,122 @@ xfbtree_get_maxrecs(
return xfbt->maxrecs[level != 0];
}
/* If this log item is a buffer item that came from the xfbtree, return it. */
static inline struct xfs_buf *
xfbtree_buf_match(
struct xfbtree *xfbt,
const struct xfs_log_item *lip)
{
const struct xfs_buf_log_item *bli;
struct xfs_buf *bp;
if (lip->li_type != XFS_LI_BUF)
return NULL;
bli = container_of(lip, struct xfs_buf_log_item, bli_item);
bp = bli->bli_buf;
if (bp->b_target != xfbt->target)
return NULL;
return bp;
}
/*
* Commit changes to the incore btree immediately by writing all dirty xfbtree
* buffers to the backing xfile. This detaches all xfbtree buffers from the
* transaction, even on failure. The buffer locks are dropped between the
* delwri queue and submit, so the caller must synchronize btree access.
*
* Normally we'd let the buffers commit with the transaction and get written to
* the xfile via the log, but online repair stages ephemeral btrees in memory
* and uses the btree_staging functions to write new btrees to disk atomically.
* The in-memory btree (and its backing store) are discarded at the end of the
* repair phase, which means that xfbtree buffers cannot commit with the rest
* of a transaction.
*
* In other words, online repair only needs the transaction to collect buffer
* pointers and to avoid buffer deadlocks, not to guarantee consistency of
* updates.
*/
int
xfbtree_trans_commit(
struct xfbtree *xfbt,
struct xfs_trans *tp)
{
struct xfs_log_item *lip, *n;
bool tp_dirty = false;
int error = 0;
/*
* For each xfbtree buffer attached to the transaction, write the dirty
* buffers to the xfile and release them.
*/
list_for_each_entry_safe(lip, n, &tp->t_items, li_trans) {
struct xfs_buf *bp = xfbtree_buf_match(xfbt, lip);
if (!bp) {
if (test_bit(XFS_LI_DIRTY, &lip->li_flags))
tp_dirty |= true;
continue;
}
trace_xfbtree_trans_commit_buf(xfbt, bp);
xmbuf_trans_bdetach(tp, bp);
/*
* If the buffer fails verification, note the failure but
* continue walking the transaction items so that we remove all
* ephemeral btree buffers.
*/
if (!error)
error = xmbuf_finalize(bp);
xfs_buf_relse(bp);
}
/*
* Reset the transaction's dirty flag to reflect the dirty state of the
* log items that are still attached.
*/
tp->t_flags = (tp->t_flags & ~XFS_TRANS_DIRTY) |
(tp_dirty ? XFS_TRANS_DIRTY : 0);
return error;
}
/*
* Cancel changes to the incore btree by detaching all the xfbtree buffers.
* Changes are not undone, so callers must not access the btree ever again.
*/
void
xfbtree_trans_cancel(
struct xfbtree *xfbt,
struct xfs_trans *tp)
{
struct xfs_log_item *lip, *n;
bool tp_dirty = false;
list_for_each_entry_safe(lip, n, &tp->t_items, li_trans) {
struct xfs_buf *bp = xfbtree_buf_match(xfbt, lip);
if (!bp) {
if (test_bit(XFS_LI_DIRTY, &lip->li_flags))
tp_dirty |= true;
continue;
}
trace_xfbtree_trans_cancel_buf(xfbt, bp);
xmbuf_trans_bdetach(tp, bp);
xfs_buf_relse(bp);
}
/*
* Reset the transaction's dirty flag to reflect the dirty state of the
* log items that are still attached.
*/
tp->t_flags = (tp->t_flags & ~XFS_TRANS_DIRTY) |
(tp_dirty ? XFS_TRANS_DIRTY : 0);
}

View File

@ -65,6 +65,9 @@ int xfbtree_free_block(struct xfs_btree_cur *cur, struct xfs_buf *bp);
int xfbtree_init(struct xfs_mount *mp, struct xfbtree *xfbt,
struct xfs_buftarg *btp, const struct xfs_btree_ops *ops);
void xfbtree_destroy(struct xfbtree *xfbt);
int xfbtree_trans_commit(struct xfbtree *xfbt, struct xfs_trans *tp);
void xfbtree_trans_cancel(struct xfbtree *xfbt, struct xfs_trans *tp);
#else
# define xfbtree_verify_bno(...) (false)
#endif /* CONFIG_XFS_BTREE_IN_MEM */

View File

@ -9,6 +9,10 @@
#include "xfs_buf_mem.h"
#include "xfs_trace.h"
#include <linux/shmem_fs.h>
#include "xfs_log_format.h"
#include "xfs_trans.h"
#include "xfs_buf_item.h"
#include "xfs_error.h"
/*
* Buffer Cache for In-Memory Files
@ -200,3 +204,67 @@ xmbuf_verify_daddr(
return daddr < (inode->i_sb->s_maxbytes >> BBSHIFT);
}
/* Discard the page backing this buffer. */
static void
xmbuf_stale(
struct xfs_buf *bp)
{
struct inode *inode = file_inode(bp->b_target->bt_file);
loff_t pos;
ASSERT(xfs_buftarg_is_mem(bp->b_target));
pos = BBTOB(xfs_buf_daddr(bp));
shmem_truncate_range(inode, pos, pos + BBTOB(bp->b_length) - 1);
}
/*
* Finalize a buffer -- discard the backing page if it's stale, or run the
* write verifier to detect problems.
*/
int
xmbuf_finalize(
struct xfs_buf *bp)
{
xfs_failaddr_t fa;
int error = 0;
if (bp->b_flags & XBF_STALE) {
xmbuf_stale(bp);
return 0;
}
/*
* Although this btree is ephemeral, validate the buffer structure so
* that we can detect memory corruption errors and software bugs.
*/
fa = bp->b_ops->verify_struct(bp);
if (fa) {
error = -EFSCORRUPTED;
xfs_verifier_error(bp, error, fa);
}
return error;
}
/*
* Detach this xmbuf buffer from the transaction by any means necessary.
* All buffers are direct-mapped, so they do not need bwrite.
*/
void
xmbuf_trans_bdetach(
struct xfs_trans *tp,
struct xfs_buf *bp)
{
struct xfs_buf_log_item *bli = bp->b_log_item;
ASSERT(bli != NULL);
bli->bli_flags &= ~(XFS_BLI_DIRTY | XFS_BLI_ORDERED |
XFS_BLI_LOGGED | XFS_BLI_STALE);
clear_bit(XFS_LI_DIRTY, &bli->bli_item.li_flags);
while (bp->b_log_item != NULL)
xfs_trans_bdetach(tp, bp);
}

View File

@ -22,6 +22,8 @@ void xmbuf_free(struct xfs_buftarg *btp);
int xmbuf_map_page(struct xfs_buf *bp);
void xmbuf_unmap_page(struct xfs_buf *bp);
bool xmbuf_verify_daddr(struct xfs_buftarg *btp, xfs_daddr_t daddr);
void xmbuf_trans_bdetach(struct xfs_trans *tp, struct xfs_buf *bp);
int xmbuf_finalize(struct xfs_buf *bp);
#else
# define xfs_buftarg_is_mem(...) (false)
# define xmbuf_map_page(...) (-ENOMEM)

View File

@ -642,6 +642,7 @@ DEFINE_BUF_ITEM_EVENT(xfs_trans_read_buf);
DEFINE_BUF_ITEM_EVENT(xfs_trans_read_buf_recur);
DEFINE_BUF_ITEM_EVENT(xfs_trans_log_buf);
DEFINE_BUF_ITEM_EVENT(xfs_trans_brelse);
DEFINE_BUF_ITEM_EVENT(xfs_trans_bdetach);
DEFINE_BUF_ITEM_EVENT(xfs_trans_bjoin);
DEFINE_BUF_ITEM_EVENT(xfs_trans_bhold);
DEFINE_BUF_ITEM_EVENT(xfs_trans_bhold_release);

View File

@ -215,6 +215,7 @@ struct xfs_buf *xfs_trans_getsb(struct xfs_trans *);
void xfs_trans_brelse(xfs_trans_t *, struct xfs_buf *);
void xfs_trans_bjoin(xfs_trans_t *, struct xfs_buf *);
void xfs_trans_bdetach(struct xfs_trans *tp, struct xfs_buf *bp);
void xfs_trans_bhold(xfs_trans_t *, struct xfs_buf *);
void xfs_trans_bhold_release(xfs_trans_t *, struct xfs_buf *);
void xfs_trans_binval(xfs_trans_t *, struct xfs_buf *);

View File

@ -392,6 +392,48 @@ xfs_trans_brelse(
xfs_buf_relse(bp);
}
/*
* Forcibly detach a buffer previously joined to the transaction. The caller
* will retain its locked reference to the buffer after this function returns.
* The buffer must be completely clean and must not be held to the transaction.
*/
void
xfs_trans_bdetach(
struct xfs_trans *tp,
struct xfs_buf *bp)
{
struct xfs_buf_log_item *bip = bp->b_log_item;
ASSERT(tp != NULL);
ASSERT(bp->b_transp == tp);
ASSERT(bip->bli_item.li_type == XFS_LI_BUF);
ASSERT(atomic_read(&bip->bli_refcount) > 0);
trace_xfs_trans_bdetach(bip);
/*
* Erase all recursion count, since we're removing this buffer from the
* transaction.
*/
bip->bli_recur = 0;
/*
* The buffer must be completely clean. Specifically, it had better
* not be dirty, stale, logged, ordered, or held to the transaction.
*/
ASSERT(!test_bit(XFS_LI_DIRTY, &bip->bli_item.li_flags));
ASSERT(!(bip->bli_flags & XFS_BLI_DIRTY));
ASSERT(!(bip->bli_flags & XFS_BLI_HOLD));
ASSERT(!(bip->bli_flags & XFS_BLI_LOGGED));
ASSERT(!(bip->bli_flags & XFS_BLI_ORDERED));
ASSERT(!(bip->bli_flags & XFS_BLI_STALE));
/* Unlink the log item from the transaction and drop the log item. */
xfs_trans_del_item(&bip->bli_item);
xfs_buf_item_put(bip);
bp->b_transp = NULL;
}
/*
* Mark the buffer as not needing to be unlocked when the buf item's
* iop_committing() routine is called. The buffer must already be locked