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);
+}
 
 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 */
 
 #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
 
        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);
+}
 
 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)
 
 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);
 
 
 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 *);
 
        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