aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDarrick J. Wong <djwong@kernel.org>2023-09-25 14:59:51 -0700
committerCarlos Maiolino <cem@kernel.org>2023-10-05 14:57:19 +0200
commit4a9f92d023934ca6d436c8ca0e7a62702540a4ba (patch)
tree4a54e30c931998633f58f9f783aa8460c2d30b9f
parent69b07d331612421f7e9b3723cc1afa527769f5c0 (diff)
downloadxfsprogs-dev-4a9f92d023934ca6d436c8ca0e7a62702540a4ba.tar.gz
xfs_db: create unlinked inodes
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>
-rw-r--r--db/iunlink.c196
-rw-r--r--libxfs/libxfs_api_defs.h1
-rw-r--r--man/man8/xfs_db.811
3 files changed, 208 insertions, 0 deletions
diff --git a/db/iunlink.c b/db/iunlink.c
index 303b5dafb7..d87562e3b0 100644
--- a/db/iunlink.c
+++ b/db/iunlink.c
@@ -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);
}
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index ddba5c7c71..04277c0095 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -149,6 +149,7 @@
#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
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 2d6d0da4d7..f53ddd67d8 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -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)