]> git.ipfire.org Git - people/ms/linux.git/blobdiff - fs/xfs/xfs_iunlink_item.c
Merge branch 'for-6.0/dax' into libnvdimm-fixes
[people/ms/linux.git] / fs / xfs / xfs_iunlink_item.c
diff --git a/fs/xfs/xfs_iunlink_item.c b/fs/xfs/xfs_iunlink_item.c
new file mode 100644 (file)
index 0000000..43005ce
--- /dev/null
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2022, Red Hat, Inc.
+ * All Rights Reserved.
+ */
+#include "xfs.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_trans.h"
+#include "xfs_trans_priv.h"
+#include "xfs_ag.h"
+#include "xfs_iunlink_item.h"
+#include "xfs_trace.h"
+#include "xfs_error.h"
+
+struct kmem_cache      *xfs_iunlink_cache;
+
+static inline struct xfs_iunlink_item *IUL_ITEM(struct xfs_log_item *lip)
+{
+       return container_of(lip, struct xfs_iunlink_item, item);
+}
+
+static void
+xfs_iunlink_item_release(
+       struct xfs_log_item     *lip)
+{
+       struct xfs_iunlink_item *iup = IUL_ITEM(lip);
+
+       xfs_perag_put(iup->pag);
+       kmem_cache_free(xfs_iunlink_cache, IUL_ITEM(lip));
+}
+
+
+static uint64_t
+xfs_iunlink_item_sort(
+       struct xfs_log_item     *lip)
+{
+       return IUL_ITEM(lip)->ip->i_ino;
+}
+
+/*
+ * Look up the inode cluster buffer and log the on-disk unlinked inode change
+ * we need to make.
+ */
+static int
+xfs_iunlink_log_dinode(
+       struct xfs_trans        *tp,
+       struct xfs_iunlink_item *iup)
+{
+       struct xfs_mount        *mp = tp->t_mountp;
+       struct xfs_inode        *ip = iup->ip;
+       struct xfs_dinode       *dip;
+       struct xfs_buf          *ibp;
+       int                     offset;
+       int                     error;
+
+       error = xfs_imap_to_bp(mp, tp, &ip->i_imap, &ibp);
+       if (error)
+               return error;
+       /*
+        * Don't log the unlinked field on stale buffers as this may be the
+        * transaction that frees the inode cluster and relogging the buffer
+        * here will incorrectly remove the stale state.
+        */
+       if (ibp->b_flags & XBF_STALE)
+               goto out;
+
+       dip = xfs_buf_offset(ibp, ip->i_imap.im_boffset);
+
+       /* Make sure the old pointer isn't garbage. */
+       if (be32_to_cpu(dip->di_next_unlinked) != iup->old_agino) {
+               xfs_inode_verifier_error(ip, -EFSCORRUPTED, __func__, dip,
+                               sizeof(*dip), __this_address);
+               error = -EFSCORRUPTED;
+               goto out;
+       }
+
+       trace_xfs_iunlink_update_dinode(mp, iup->pag->pag_agno,
+                       XFS_INO_TO_AGINO(mp, ip->i_ino),
+                       be32_to_cpu(dip->di_next_unlinked), iup->next_agino);
+
+       dip->di_next_unlinked = cpu_to_be32(iup->next_agino);
+       offset = ip->i_imap.im_boffset +
+                       offsetof(struct xfs_dinode, di_next_unlinked);
+
+       xfs_dinode_calc_crc(mp, dip);
+       xfs_trans_inode_buf(tp, ibp);
+       xfs_trans_log_buf(tp, ibp, offset, offset + sizeof(xfs_agino_t) - 1);
+       return 0;
+out:
+       xfs_trans_brelse(tp, ibp);
+       return error;
+}
+
+/*
+ * On precommit, we grab the inode cluster buffer for the inode number we were
+ * passed, then update the next unlinked field for that inode in the buffer and
+ * log the buffer. This ensures that the inode cluster buffer was logged in the
+ * correct order w.r.t. other inode cluster buffers. We can then remove the
+ * iunlink item from the transaction and release it as it is has now served it's
+ * purpose.
+ */
+static int
+xfs_iunlink_item_precommit(
+       struct xfs_trans        *tp,
+       struct xfs_log_item     *lip)
+{
+       struct xfs_iunlink_item *iup = IUL_ITEM(lip);
+       int                     error;
+
+       error = xfs_iunlink_log_dinode(tp, iup);
+       list_del(&lip->li_trans);
+       xfs_iunlink_item_release(lip);
+       return error;
+}
+
+static const struct xfs_item_ops xfs_iunlink_item_ops = {
+       .iop_release    = xfs_iunlink_item_release,
+       .iop_sort       = xfs_iunlink_item_sort,
+       .iop_precommit  = xfs_iunlink_item_precommit,
+};
+
+
+/*
+ * Initialize the inode log item for a newly allocated (in-core) inode.
+ *
+ * Inode extents can only reside within an AG. Hence specify the starting
+ * block for the inode chunk by offset within an AG as well as the
+ * length of the allocated extent.
+ *
+ * This joins the item to the transaction and marks it dirty so
+ * that we don't need a separate call to do this, nor does the
+ * caller need to know anything about the iunlink item.
+ */
+int
+xfs_iunlink_log_inode(
+       struct xfs_trans        *tp,
+       struct xfs_inode        *ip,
+       struct xfs_perag        *pag,
+       xfs_agino_t             next_agino)
+{
+       struct xfs_mount        *mp = tp->t_mountp;
+       struct xfs_iunlink_item *iup;
+
+       ASSERT(xfs_verify_agino_or_null(pag, next_agino));
+       ASSERT(xfs_verify_agino_or_null(pag, ip->i_next_unlinked));
+
+       /*
+        * Since we're updating a linked list, we should never find that the
+        * current pointer is the same as the new value, unless we're
+        * terminating the list.
+        */
+       if (ip->i_next_unlinked == next_agino) {
+               if (next_agino != NULLAGINO)
+                       return -EFSCORRUPTED;
+               return 0;
+       }
+
+       iup = kmem_cache_zalloc(xfs_iunlink_cache, GFP_KERNEL | __GFP_NOFAIL);
+       xfs_log_item_init(mp, &iup->item, XFS_LI_IUNLINK,
+                         &xfs_iunlink_item_ops);
+
+       iup->ip = ip;
+       iup->next_agino = next_agino;
+       iup->old_agino = ip->i_next_unlinked;
+
+       atomic_inc(&pag->pag_ref);
+       iup->pag = pag;
+
+       xfs_trans_add_item(tp, &iup->item);
+       tp->t_flags |= XFS_TRANS_DIRTY;
+       set_bit(XFS_LI_DIRTY, &iup->item.li_flags);
+       return 0;
+}
+