]> git.apps.os.sepia.ceph.com Git - xfsprogs-dev.git/commitdiff
xfs_db: create unlinked inodes
authorDarrick J. Wong <djwong@kernel.org>
Mon, 25 Sep 2023 21:59:51 +0000 (14:59 -0700)
committerCarlos Maiolino <cem@kernel.org>
Thu, 5 Oct 2023 12:57:19 +0000 (14:57 +0200)
Create an expert-mode debugger command to create unlinked inodes.
This will hopefully aid in simulation of leaked unlinked inode handling
in the kernel and elsewhere.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Bill O'Donnell <bodonnel@redhat.com>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
db/iunlink.c
libxfs/libxfs_api_defs.h
man/man8/xfs_db.8

index 303b5dafb7d38660be6ea852dc4469aa8a5585ce..d87562e3b0a2c020b09e9e28e23675e26e3ff2ab 100644 (file)
@@ -197,8 +197,204 @@ static const cmdinfo_t    dump_iunlinked_cmd =
          N_("[-a agno] [-b bucket] [-q] [-v]"),
          N_("dump chain of unlinked inode buckets"), NULL };
 
+/*
+ * Look up the inode cluster buffer and log the on-disk unlinked inode change
+ * we need to make.
+ */
+static int
+iunlink_log_dinode(
+       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_dinode       *dip;
+       struct xfs_buf          *ibp;
+       int                     offset;
+       int                     error;
+
+       error = -libxfs_imap_to_bp(mp, tp, &ip->i_imap, &ibp);
+       if (error)
+               return error;
+
+       dip = xfs_buf_offset(ibp, ip->i_imap.im_boffset);
+
+       dip->di_next_unlinked = cpu_to_be32(next_agino);
+       offset = ip->i_imap.im_boffset +
+                       offsetof(struct xfs_dinode, di_next_unlinked);
+
+       libxfs_dinode_calc_crc(mp, dip);
+       libxfs_trans_log_buf(tp, ibp, offset, offset + sizeof(xfs_agino_t) - 1);
+       return 0;
+}
+
+static int
+iunlink_insert_inode(
+       struct xfs_trans        *tp,
+       struct xfs_perag        *pag,
+       struct xfs_buf          *agibp,
+       struct xfs_inode        *ip)
+{
+       struct xfs_mount        *mp = tp->t_mountp;
+       struct xfs_agi          *agi = agibp->b_addr;
+       xfs_agino_t             next_agino;
+       xfs_agino_t             agino = XFS_INO_TO_AGINO(mp, ip->i_ino);
+       short                   bucket_index = agino % XFS_AGI_UNLINKED_BUCKETS;
+       int                     offset;
+       int                     error;
+
+       /*
+        * Get the index into the agi hash table for the list this inode will
+        * go on.  Make sure the pointer isn't garbage and that this inode
+        * isn't already on the list.
+        */
+       next_agino = be32_to_cpu(agi->agi_unlinked[bucket_index]);
+       if (next_agino == agino || !xfs_verify_agino_or_null(pag, next_agino))
+               return EFSCORRUPTED;
+
+       if (next_agino != NULLAGINO) {
+               /*
+                * There is already another inode in the bucket, so point this
+                * inode to the current head of the list.
+                */
+               error = iunlink_log_dinode(tp, ip, pag, next_agino);
+               if (error)
+                       return error;
+       }
+
+       /* Update the bucket. */
+       agi->agi_unlinked[bucket_index] = cpu_to_be32(agino);
+       offset = offsetof(struct xfs_agi, agi_unlinked) +
+                       (sizeof(xfs_agino_t) * bucket_index);
+       libxfs_trans_log_buf(tp, agibp, offset,
+                       offset + sizeof(xfs_agino_t) - 1);
+       return 0;
+}
+
+/*
+ * This is called when the inode's link count has gone to 0 or we are creating
+ * a tmpfile via O_TMPFILE.  The inode @ip must have nlink == 0.
+ *
+ * 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
+iunlink(
+       struct xfs_trans        *tp,
+       struct xfs_inode        *ip)
+{
+       struct xfs_mount        *mp = tp->t_mountp;
+       struct xfs_perag        *pag;
+       struct xfs_buf          *agibp;
+       int                     error;
+
+       ASSERT(VFS_I(ip)->i_nlink == 0);
+       ASSERT(VFS_I(ip)->i_mode != 0);
+
+       pag = libxfs_perag_get(mp, XFS_INO_TO_AGNO(mp, ip->i_ino));
+
+       /* Get the agi buffer first.  It ensures lock ordering on the list. */
+       error = -libxfs_read_agi(pag, tp, &agibp);
+       if (error)
+               goto out;
+
+       error = iunlink_insert_inode(tp, pag, agibp, ip);
+out:
+       libxfs_perag_put(pag);
+       return error;
+}
+
+static int
+create_unlinked(
+       struct xfs_mount        *mp)
+{
+       struct cred             cr = { };
+       struct fsxattr          fsx = { };
+       struct xfs_inode        *ip;
+       struct xfs_trans        *tp;
+       unsigned int            resblks;
+       int                     error;
+
+       resblks = XFS_IALLOC_SPACE_RES(mp);
+       error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_create_tmpfile, resblks,
+                       0, 0, &tp);
+       if (error) {
+               dbprintf(_("alloc trans: %s\n"), strerror(error));
+               return error;
+       }
+
+       error = -libxfs_dir_ialloc(&tp, NULL, S_IFREG | 0600, 0, 0, &cr, &fsx,
+                       &ip);
+       if (error) {
+               dbprintf(_("create inode: %s\n"), strerror(error));
+               goto out_cancel;
+       }
+
+       error = iunlink(tp, ip);
+       if (error) {
+               dbprintf(_("unlink inode: %s\n"), strerror(error));
+               goto out_rele;
+       }
+
+       error = -libxfs_trans_commit(tp);
+       if (error)
+               dbprintf(_("commit inode: %s\n"), strerror(error));
+
+       dbprintf(_("Created unlinked inode %llu in agno %u\n"),
+                       (unsigned long long)ip->i_ino,
+                       XFS_INO_TO_AGNO(mp, ip->i_ino));
+       libxfs_irele(ip);
+       return error;
+out_rele:
+       libxfs_irele(ip);
+out_cancel:
+       libxfs_trans_cancel(tp);
+       return error;
+}
+
+static int
+iunlink_f(
+       int             argc,
+       char            **argv)
+{
+       int             nr = 1;
+       int             c;
+       int             error;
+
+       while ((c = getopt(argc, argv, "n:")) != EOF) {
+               switch (c) {
+               case 'n':
+                       nr = atoi(optarg);
+                       if (nr <= 0) {
+                               dbprintf(_("%s: need positive number\n"));
+                               return 0;
+                       }
+                       break;
+               default:
+                       dbprintf(_("Bad option for iunlink command.\n"));
+                       return 0;
+               }
+       }
+
+       for (c = 0; c < nr; c++) {
+               error = create_unlinked(mp);
+               if (error)
+                       return 1;
+       }
+
+       return 0;
+}
+
+static const cmdinfo_t iunlink_cmd =
+       { "iunlink", NULL, iunlink_f, 0, -1, 0,
+         N_("[-n nr]"),
+         N_("allocate inodes and put them on the unlinked list"), NULL };
+
 void
 iunlink_init(void)
 {
        add_command(&dump_iunlinked_cmd);
+       if (expert_mode)
+               add_command(&iunlink_cmd);
 }
index ddba5c7c71dbde61a2f8f08ce138aa4ed8dfd451..04277c00955b7f1e7fe6a3db9275f558fe35abea 100644 (file)
 #define xfs_prealloc_blocks            libxfs_prealloc_blocks
 
 #define xfs_read_agf                   libxfs_read_agf
+#define xfs_read_agi                   libxfs_read_agi
 #define xfs_refc_block                 libxfs_refc_block
 #define xfs_refcountbt_calc_reserves   libxfs_refcountbt_calc_reserves
 #define xfs_refcountbt_calc_size       libxfs_refcountbt_calc_size
index 2d6d0da4d7bee9e97603c0a2322c1559a5c4c5db..f53ddd67d87c0cfb15b711cf35bc0f7c73cdc062 100644 (file)
@@ -840,6 +840,17 @@ Set the current inode number. If no
 .I inode#
 is given, print the current inode number.
 .TP
+.BI "iunlink [-n " nr " ]"
+Allocate inodes and put them on the unlinked list.
+
+Options include:
+.RS 1.0i
+.TP 0.4i
+.B \-n
+Create this number of unlinked inodes.
+If not specified, 1 inode will be created.
+.RE
+.TP
 .BI "label [" label ]
 Set the filesystem label. The filesystem label can be used by
 .BR mount (8)