]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs_db: add link and unlink expert commands
authorDarrick J. Wong <djwong@kernel.org>
Mon, 29 Jul 2024 23:23:23 +0000 (16:23 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Tue, 30 Jul 2024 00:01:12 +0000 (17:01 -0700)
Create a pair of commands to create and remove directory entries to
support functional testing of directory tree corruption.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
db/namei.c
include/xfs_inode.h
libxfs/libxfs_api_defs.h
man/man8/xfs_db.8

index 46b4cacb507e54bc50d70e83ca861c23a2f0444b..d57ead4f165d809b466c8c5fb67274d3b13dee26 100644 (file)
@@ -916,6 +916,376 @@ static struct cmdinfo parent_cmd = {
        .help           = parent_help,
 };
 
+static void
+link_help(void)
+{
+       dbprintf(_(
+"\n"
+" Create a directory entry in the current directory that points to the\n"
+" specified file.\n"
+"\n"
+" Options:\n"
+"   -i   -- Point to this specific inode number.\n"
+"   -p   -- Point to the inode given by this path.\n"
+"   -t   -- Set the file type to this value.\n"
+"   name -- Create this directory entry with this name.\n"
+       ));
+}
+
+static int
+create_child(
+       struct xfs_mount        *mp,
+       xfs_ino_t               parent_ino,
+       const char              *name,
+       unsigned int            ftype,
+       xfs_ino_t               child_ino)
+{
+       struct xfs_name         xname = {
+               .name           = (const unsigned char *)name,
+               .len            = strlen(name),
+               .type           = ftype,
+       };
+       struct xfs_parent_args  *ppargs = NULL;
+       struct xfs_trans        *tp;
+       struct xfs_inode        *dp, *ip;
+       unsigned int            resblks;
+       bool                    isdir;
+       int                     error;
+
+       error = -libxfs_iget(mp, NULL, parent_ino, 0, &dp);
+       if (error)
+               return error;
+
+       if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+               error = -ENOTDIR;
+               goto out_dp;
+       }
+
+       error = -libxfs_iget(mp, NULL, child_ino, 0, &ip);
+       if (error)
+               goto out_dp;
+       isdir = S_ISDIR(VFS_I(ip)->i_mode);
+
+       if (xname.type == XFS_DIR3_FT_UNKNOWN)
+               xname.type = libxfs_mode_to_ftype(VFS_I(ip)->i_mode);
+
+       error = -libxfs_parent_start(mp, &ppargs);
+       if (error)
+               goto out_ip;
+
+       resblks = libxfs_link_space_res(mp, MAXNAMELEN);
+       error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_link, resblks, 0, 0,
+                       &tp);
+       if (error)
+               goto out_parent;
+
+       libxfs_trans_ijoin(tp, dp, 0);
+       libxfs_trans_ijoin(tp, ip, 0);
+
+       error = -libxfs_dir_createname(tp, dp, &xname, ip->i_ino, resblks);
+       if (error)
+               goto out_trans;
+
+       /* bump dp's link to ip */
+       libxfs_bumplink(tp, ip);
+
+       /* bump ip's dotdot link to dp */
+       if (isdir)
+               libxfs_bumplink(tp, dp);
+
+       /* Replace the dotdot entry in the child directory. */
+       if (isdir) {
+               error = -libxfs_dir_replace(tp, ip, &xfs_name_dotdot,
+                               dp->i_ino, resblks);
+               if (error)
+                       goto out_trans;
+       }
+
+       if (ppargs) {
+               error = -libxfs_parent_addname(tp, ppargs, dp, &xname, ip);
+               if (error)
+                       goto out_trans;
+       }
+
+       error = -libxfs_trans_commit(tp);
+       goto out_parent;
+
+out_trans:
+       libxfs_trans_cancel(tp);
+out_parent:
+       libxfs_parent_finish(mp, ppargs);
+out_ip:
+       libxfs_irele(ip);
+out_dp:
+       libxfs_irele(dp);
+       return error;
+}
+
+static const char *ftype_map[] = {
+       [XFS_DIR3_FT_REG_FILE]  = "reg",
+       [XFS_DIR3_FT_DIR]       = "dir",
+       [XFS_DIR3_FT_CHRDEV]    = "cdev",
+       [XFS_DIR3_FT_BLKDEV]    = "bdev",
+       [XFS_DIR3_FT_FIFO]      = "fifo",
+       [XFS_DIR3_FT_SOCK]      = "sock",
+       [XFS_DIR3_FT_SYMLINK]   = "symlink",
+       [XFS_DIR3_FT_WHT]       = "whiteout",
+};
+
+static int
+link_f(
+       int                     argc,
+       char                    **argv)
+{
+       xfs_ino_t               child_ino = NULLFSINO;
+       int                     ftype = XFS_DIR3_FT_UNKNOWN;
+       unsigned int            i;
+       int                     c;
+       int                     error = 0;
+
+       while ((c = getopt(argc, argv, "i:p:t:")) != -1) {
+               switch (c) {
+               case 'i':
+                       errno = 0;
+                       child_ino = strtoull(optarg, NULL, 0);
+                       if (errno == ERANGE) {
+                               printf("%s: unknown inode number\n", optarg);
+                               exitcode = 1;
+                               return 0;
+                       }
+                       break;
+               case 'p':
+                       push_cur();
+                       error = path_walk(optarg);
+                       if (error) {
+                               printf("%s: %s\n", optarg, strerror(error));
+                               exitcode = 1;
+                               return 0;
+                       } else if (iocur_top->typ != &typtab[TYP_INODE]) {
+                               printf("%s: does not point to an inode\n",
+                                               optarg);
+                               exitcode = 1;
+                               return 0;
+                       } else {
+                               child_ino = iocur_top->ino;
+                       }
+                       pop_cur();
+                       break;
+               case 't':
+                       for (i = 0; i < ARRAY_SIZE(ftype_map); i++) {
+                               if (ftype_map[i] &&
+                                   !strcmp(ftype_map[i], optarg)) {
+                                       ftype = i;
+                                       break;
+                               }
+                       }
+                       if (i == ARRAY_SIZE(ftype_map)) {
+                               printf("%s: unknown file type\n", optarg);
+                               exitcode = 1;
+                               return 0;
+                       }
+                       break;
+               default:
+                       link_help();
+                       return 0;
+               }
+       }
+
+       if (child_ino == NULLFSINO) {
+               printf("link: need to specify child via -i or -p\n");
+               exitcode = 1;
+               return 0;
+       }
+
+       if (iocur_top->typ != &typtab[TYP_INODE]) {
+               printf("io cursor does not point to an inode.\n");
+               exitcode = 1;
+               return 0;
+       }
+
+       if (optind + 1 != argc) {
+               printf("link: need directory entry name");
+               exitcode = 1;
+               return 0;
+       }
+
+       error = create_child(mp, iocur_top->ino, argv[optind], ftype,
+                       child_ino);
+       if (error) {
+               printf("link failed: %s\n", strerror(error));
+               exitcode = 1;
+               return 0;
+       }
+
+       return 0;
+}
+
+static struct cmdinfo link_cmd = {
+       .name           = "link",
+       .cfunc          = link_f,
+       .argmin         = 0,
+       .argmax         = -1,
+       .canpush        = 0,
+       .args           = "[-i ino] [-p path] [-t ftype] name",
+       .help           = link_help,
+};
+
+static void
+unlink_help(void)
+{
+       dbprintf(_(
+"\n"
+" Remove a directory entry from the current directory.\n"
+"\n"
+" Options:\n"
+"   name -- Remove the directory entry with this name.\n"
+       ));
+}
+
+static void
+droplink(
+       struct xfs_trans        *tp,
+       struct xfs_inode        *ip)
+{
+       struct inode            *inode = VFS_I(ip);
+
+       libxfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
+
+       if (inode->i_nlink != XFS_NLINK_PINNED)
+               drop_nlink(VFS_I(ip));
+
+       libxfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+}
+
+static int
+remove_child(
+       struct xfs_mount        *mp,
+       xfs_ino_t               parent_ino,
+       const char              *name)
+{
+       struct xfs_name         xname = {
+               .name           = (const unsigned char *)name,
+               .len            = strlen(name),
+       };
+       struct xfs_parent_args  *ppargs;
+       struct xfs_trans        *tp;
+       struct xfs_inode        *dp, *ip;
+       xfs_ino_t               child_ino;
+       unsigned int            resblks;
+       int                     error;
+
+       error = -libxfs_iget(mp, NULL, parent_ino, 0, &dp);
+       if (error)
+               return error;
+
+       if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+               error = -ENOTDIR;
+               goto out_dp;
+       }
+
+       error = -libxfs_dir_lookup(NULL, dp, &xname, &child_ino, NULL);
+       if (error)
+               goto out_dp;
+
+       error = -libxfs_iget(mp, NULL, child_ino, 0, &ip);
+       if (error)
+               goto out_dp;
+
+       error = -libxfs_parent_start(mp, &ppargs);
+       if (error)
+               goto out_ip;
+
+       resblks = libxfs_remove_space_res(mp, MAXNAMELEN);
+       error = -libxfs_trans_alloc(mp, &M_RES(mp)->tr_remove, resblks, 0, 0,
+                       &tp);
+       if (error)
+               goto out_parent;
+
+       libxfs_trans_ijoin(tp, dp, 0);
+       libxfs_trans_ijoin(tp, ip, 0);
+
+       if (S_ISDIR(VFS_I(ip)->i_mode)) {
+               /* drop ip's dotdot link to dp */
+               droplink(tp, dp);
+       } else {
+               libxfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
+       }
+
+       /* drop dp's link to ip */
+       droplink(tp, ip);
+
+       error = -libxfs_dir_removename(tp, dp, &xname, ip->i_ino, resblks);
+       if (error)
+               goto out_trans;
+
+       if (ppargs) {
+               error = -libxfs_parent_removename(tp, ppargs, dp, &xname, ip);
+               if (error)
+                       goto out_trans;
+       }
+
+       error = -libxfs_trans_commit(tp);
+       goto out_parent;
+
+out_trans:
+       libxfs_trans_cancel(tp);
+out_parent:
+       libxfs_parent_finish(mp, ppargs);
+out_ip:
+       libxfs_irele(ip);
+out_dp:
+       libxfs_irele(dp);
+       return error;
+}
+
+static int
+unlink_f(
+       int                     argc,
+       char                    **argv)
+{
+       int                     c;
+       int                     error = 0;
+
+       while ((c = getopt(argc, argv, "")) != -1) {
+               switch (c) {
+               default:
+                       unlink_help();
+                       return 0;
+               }
+       }
+
+       if (iocur_top->typ != &typtab[TYP_INODE]) {
+               printf("io cursor does not point to an inode.\n");
+               exitcode = 1;
+               return 0;
+       }
+
+       if (optind + 1 != argc) {
+               printf("unlink: need directory entry name");
+               exitcode = 1;
+               return 0;
+       }
+
+       error = remove_child(mp, iocur_top->ino, argv[optind]);
+       if (error) {
+               printf("unlink failed: %s\n", strerror(error));
+               exitcode = 1;
+               return 0;
+       }
+
+       return 0;
+}
+
+static struct cmdinfo unlink_cmd = {
+       .name           = "unlink",
+       .cfunc          = unlink_f,
+       .argmin         = 0,
+       .argmax         = -1,
+       .canpush        = 0,
+       .args           = "name",
+       .help           = unlink_help,
+};
+
 void
 namei_init(void)
 {
@@ -927,4 +1297,12 @@ namei_init(void)
 
        parent_cmd.oneline = _("list parent pointers");
        add_command(&parent_cmd);
+
+       if (expert_mode) {
+               link_cmd.oneline = _("create directory link");
+               add_command(&link_cmd);
+
+               unlink_cmd.oneline = _("remove directory link");
+               add_command(&unlink_cmd);
+       }
 }
index 45339b42621a619b8ea061c27ccea93d1f0992f0..9bbf372251973cde77ef3dcef6bea63b4a988b1c 100644 (file)
@@ -315,6 +315,10 @@ static inline void inc_nlink(struct inode *inode)
 {
        inode->i_nlink++;
 }
+static inline void drop_nlink(struct inode *inode)
+{
+       inode->i_nlink--;
+}
 
 static inline bool xfs_is_reflink_inode(struct xfs_inode *ip)
 {
index bceaab8baa8c1e81bdb19008f7fb2a4ada29e5b0..b7edaf788051b5b6af5ade989b8a20dac721a15e 100644 (file)
 #define xfs_dir_init                   libxfs_dir_init
 #define xfs_dir_ino_validate           libxfs_dir_ino_validate
 #define xfs_dir_lookup                 libxfs_dir_lookup
+#define xfs_dir_removename             libxfs_dir_removename
 #define xfs_dir_replace                        libxfs_dir_replace
 
 #define xfs_dqblk_repair               libxfs_dqblk_repair
 #define xfs_dquot_from_disk_ts         libxfs_dquot_from_disk_ts
 #define xfs_dquot_verify               libxfs_dquot_verify
 
+#define xfs_bumplink                   libxfs_bumplink
+#define xfs_droplink                   libxfs_droplink
+
 #define xfs_finobt_calc_reserves       libxfs_finobt_calc_reserves
 #define xfs_finobt_init_cursor         libxfs_finobt_init_cursor
 #define xfs_free_extent                        libxfs_free_extent
 
 #define xfs_iread_extents              libxfs_iread_extents
 #define xfs_irele                      libxfs_irele
+#define xfs_link_space_res             libxfs_link_space_res
 #define xfs_log_calc_minimum_size      libxfs_log_calc_minimum_size
 #define xfs_log_get_max_trans_res      libxfs_log_get_max_trans_res
 #define xfs_log_sb                     libxfs_log_sb
 #define xfs_mode_to_ftype              libxfs_mode_to_ftype
 #define xfs_mkdir_space_res            libxfs_mkdir_space_res
-#define xfs_parent_add                 libxfs_parent_add
+#define xfs_parent_addname             libxfs_parent_addname
 #define xfs_parent_finish              libxfs_parent_finish
+#define xfs_parent_removename          libxfs_parent_removename
 #define xfs_parent_start               libxfs_parent_start
 #define xfs_parent_from_attr           libxfs_parent_from_attr
 #define xfs_perag_get                  libxfs_perag_get
index a561bdc492d4f3f681f857568df807c67c9f3a84..f8db6c36f40a55e537929688c0a3fe2ee6d7688a 100644 (file)
@@ -901,6 +901,21 @@ will result in truncation and a warning will be issued. If no
 .I label
 is given, the current filesystem label is printed.
 .TP
+.BI "link [-i " ino "] [-p " path "] [-t " ftype "] name"
+In the current directory, create a directory entry with the given
+.I name
+pointing to a file.
+The file must be specified either as a directory tree path as given by the
+.I path
+option; or directly as an inode number as given by the
+.I ino
+option.
+The file type in the directory entry will be determined from the mode of the
+child file unless the
+.I ftype
+option is given.
+The file being targetted must not be on the iunlink list.
+.TP
 .BI "log [stop | start " filename ]
 Start logging output to
 .IR filename ,
@@ -1052,6 +1067,11 @@ Print the timestamps in the current locale's date and time format instead of
 raw seconds since the Unix epoch.
 .RE
 .TP
+.BI "unlink name"
+In the current directory, remove a directory entry with the given
+.IR name .
+The file being targetted will not be put on the iunlink list.
+.TP
 .BI "uuid [" uuid " | " generate " | " rewrite " | " restore ]
 Set the filesystem universally unique identifier (UUID).
 The filesystem UUID can be used by