]> www.infradead.org Git - users/hch/xfsprogs.git/commitdiff
spaceman: move inodes with hardlinks
authorDave Chinner <dchinner@redhat.com>
Wed, 7 Aug 2024 22:54:54 +0000 (15:54 -0700)
committerDarrick J. Wong <djwong@kernel.org>
Wed, 14 Aug 2024 03:08:26 +0000 (20:08 -0700)
When a inode to be moved to a different AG has multiple hard links,
we need to "move" all the hard links, too. To do this, we need to
create temporary hardlinks to the new file, and then use rename
exchange to swap all the hardlinks that point to the old inode
with new hardlinks that point to the new inode.

We already know that an inode has hard links via the path discovery,
and we can check it against the link count that is reported for the
inode before we start building the link farm.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Darrick J. Wong <djwong@kernel.org>
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
spaceman/find_owner.c
spaceman/move_inode.c
spaceman/relocation.c
spaceman/relocation.h

index 8e93145539a227b60291c991146dc6c866953d5a..1984d0ee7ca5f6b8f0c9a0f0240f2a574821ed3b 100644 (file)
@@ -240,7 +240,6 @@ resolve_owner_cb(
        struct FTW              *data)
 {
        struct inode_path       *ipath, *slot_ipath;
-       int                     pathlen;
        struct inode_path       **slot;
 
        /*
@@ -260,17 +259,9 @@ _("Failed to obtain stat(2) information from path %s. Aborting\n"),
        }
 
        /* Allocate a new inode path and record the path in it. */
-       pathlen = strlen(path);
-       ipath = calloc(1, sizeof(*ipath) + pathlen + 1);
-       if (!ipath) {
-               fprintf(stderr,
-_("Aborting: Storing path %s for inode 0x%lx failed: %s\n"),
-                       path, stat->st_ino, strerror(ENOMEM));
+       ipath = ipath_alloc(path, stat);
+       if (!ipath)
                return -ENOMEM;
-       }
-       INIT_LIST_HEAD(&ipath->path_list);
-       memcpy(&ipath->path[0], path, pathlen);
-       ipath->ino = stat->st_ino;
 
        /*
         * If the slot contains the inode number we just looked up, then we
index ab3c12f5de987b313dcda46948351ce52bdcde40..3a182929579e45b0a3e03a61ae7a82167eb790e2 100644 (file)
@@ -36,12 +36,14 @@ create_tmpfile(
        struct xfs_fd   *xfd,
        xfs_agnumber_t  agno,
        char            **tmpfile,
-       int             *tmpfd)
+       int             *tmpfd,
+       int             link_count)
 {
        char            name[PATH_MAX + 1];
+       char            linkname[PATH_MAX + 1];
        mode_t          mask;
        int             fd;
-       int             i;
+       int             i, j;
        int             ret;
 
        /* construct tmpdir */
@@ -105,14 +107,36 @@ create_tmpfile(
                fprintf(stderr, _("cannot create tmpfile: %s: %s\n"),
                       name, strerror(errno));
                ret = -errno;
+               goto out_cleanup_dir;
+       }
+
+       /* Create hard links to temporary file. */
+       for (j = link_count; j > 1; i--) {
+               snprintf(linkname, PATH_MAX, "%s/.spaceman/dir%d/tmpfile.%d.hardlink.%d", mnt, i, getpid(), j);
+               ret = link(name, linkname);
+               if (ret < 0) {
+                       fprintf(stderr, _("cannot create hardlink: %s: %s\n"),
+                              linkname, strerror(errno));
+                       ret = -errno;
+                       goto out_cleanup_links;
+               }
        }
 
+
        /* return name and fd */
        (void)umask(mask);
        *tmpfd = fd;
        *tmpfile = strdup(name);
 
        return 0;
+
+out_cleanup_links:
+       for (; j <= link_count; j++) {
+               snprintf(linkname, PATH_MAX, "%s/.spaceman/dir%d/tmpfile.%d.hardlink.%d", mnt, i, getpid(), j);
+               unlink(linkname);
+       }
+       close(fd);
+       unlink(name);
 out_cleanup_dir:
        snprintf(name, PATH_MAX, "%s/.spaceman", mnt);
        rmdir(name);
@@ -405,21 +429,53 @@ exchange_inodes(
        return 0;
 }
 
+static int
+exchange_hardlinks(
+       struct inode_path       *ipath,
+       const char              *tmpfile)
+{
+       char                    linkname[PATH_MAX];
+       struct inode_path       *linkpath;
+       int                     i = 2;
+       int                     ret;
+
+       list_for_each_entry(linkpath, &ipath->path_list, path_list) {
+               if (i++ > ipath->link_count) {
+                       fprintf(stderr, "ipath link count mismatch!\n");
+                       return 0;
+               }
+
+               snprintf(linkname, PATH_MAX, "%s.hardlink.%d", tmpfile, i);
+               ret = renameat2(AT_FDCWD, linkname,
+                               AT_FDCWD, linkpath->path, RENAME_EXCHANGE);
+               if (ret) {
+                       fprintf(stderr,
+               "failed to exchange hard link %s with %s: %s\n",
+                               linkname, linkpath->path, strerror(errno));
+                       return -errno;
+               }
+       }
+       return 0;
+}
+
 int
 relocate_file_to_ag(
        const char              *mnt,
-       const char              *path,
+       struct inode_path       *ipath,
        struct xfs_fd           *xfd,
        xfs_agnumber_t          agno)
 {
        int                     ret;
        int                     tmpfd = -1;
        char                    *tmpfile = NULL;
+       int                     i;
 
-       fprintf(stderr, "move mnt %s, path %s, agno %d\n", mnt, path, agno);
+       fprintf(stderr, "move mnt %s, path %s, agno %d\n",
+                       mnt, ipath->path, agno);
 
        /* create temporary file in agno */
-       ret = create_tmpfile(mnt, xfd, agno, &tmpfile, &tmpfd);
+       ret = create_tmpfile(mnt, xfd, agno, &tmpfile, &tmpfd,
+                               ipath->link_count);
        if (ret)
                return ret;
 
@@ -444,12 +500,28 @@ relocate_file_to_ag(
                goto out_cleanup;
 
        /* swap the inodes over */
-       ret = exchange_inodes(xfd, tmpfd, tmpfile, path);
+       ret = exchange_inodes(xfd, tmpfd, tmpfile, ipath->path);
+       if (ret)
+               goto out_cleanup;
+
+       /* swap the hard links over */
+       ret = exchange_hardlinks(ipath, tmpfile);
+       if (ret)
+               goto out_cleanup;
 
 out_cleanup:
        if (ret == -1)
                ret = -errno;
 
+       /* remove old hard links */
+       for (i = 2; i <= ipath->link_count; i++) {
+               char linkname[PATH_MAX + 256]; // anti-warning-crap
+
+               snprintf(linkname, PATH_MAX + 256, "%s.hardlink.%d", tmpfile, i);
+               unlink(linkname);
+       }
+
+       /* remove tmpfile */
        close(tmpfd);
        if (tmpfile)
                unlink(tmpfile);
@@ -458,11 +530,32 @@ out_cleanup:
        return ret;
 }
 
+static int
+build_ipath(
+       const char              *path,
+       struct stat             *st,
+       struct inode_path       **ipathp)
+{
+       struct inode_path       *ipath;
+
+       *ipathp = NULL;
+
+       ipath = ipath_alloc(path, st);
+       if (!ipath)
+               return -ENOMEM;
+
+       /* we only move a single path with move_inode */
+       ipath->link_count = 1;
+       *ipathp = ipath;
+       return 0;
+}
+
 static int
 move_inode_f(
        int                     argc,
        char                    **argv)
 {
+       struct inode_path       *ipath = NULL;
        void                    *fshandle;
        size_t                  fshdlen;
        xfs_agnumber_t          agno = 0;
@@ -511,24 +604,30 @@ _("Destination AG %d does not exist. Filesystem only has %d AGs\n"),
                goto exit_fail;
        }
 
-       if (S_ISREG(st.st_mode)) {
-               ret = relocate_file_to_ag(file->fs_path.fs_dir, file->name,
-                               &file->xfd, agno);
-       } else {
+       if (!S_ISREG(st.st_mode)) {
                fprintf(stderr, _("Unsupported: %s is not a regular file.\n"),
                        file->name);
                goto exit_fail;
        }
 
+       ret = build_ipath(file->name, &st, &ipath);
+       if (ret)
+               goto exit_fail;
+
+       ret = relocate_file_to_ag(file->fs_path.fs_dir, ipath,
+                               &file->xfd, agno);
        if (ret) {
                fprintf(stderr, _("Failed to move inode to AG %d: %s\n"),
                        agno, strerror(-ret));
                goto exit_fail;
        }
+       free(ipath);
        fshandle_destroy();
        return 0;
 
 exit_fail:
+       if (ipath)
+               free(ipath);
        fshandle_destroy();
        exitcode = 1;
        return 0;
index 7b125cc0ae12b003ff7ed067de738dd000f4fa99..b0960272168510e070c16806bcafb83a2d50a285 100644 (file)
@@ -318,6 +318,30 @@ forget_reloc_ino(
 
 static struct cmdinfo relocate_cmd;
 
+struct inode_path *
+ipath_alloc(
+       const char              *path,
+       const struct stat       *stat)
+{
+       struct inode_path       *ipath;
+       int                     pathlen = strlen(path);
+
+       /* Allocate a new inode path and record the path in it. */
+       ipath = calloc(1, sizeof(*ipath) + pathlen + 1);
+       if (!ipath) {
+               fprintf(stderr,
+_("Failed to allocate ipath %s for inode 0x%llx failed: %s\n"),
+                       path, (unsigned long long)stat->st_ino,
+                       strerror(-errno));
+               return NULL;
+       }
+       INIT_LIST_HEAD(&ipath->path_list);
+       memcpy(&ipath->path[0], path, pathlen);
+       ipath->ino = stat->st_ino;
+
+       return ipath;
+}
+
 static int
 relocate_targets_to_ag(
        const char              *mnt,
@@ -336,15 +360,6 @@ relocate_targets_to_ag(
                if (!ipath)
                        break;
 
-               /* XXX: don't handle hard link cases yet */
-               if (ipath->link_count > 1) {
-                       fprintf(stderr,
-               "FIXME! Skipping hardlinked inode at path %s\n",
-                               ipath->path);
-                       goto next;
-               }
-
-
                ret = stat(ipath->path, &st);
                if (ret) {
                        fprintf(stderr, _("stat(%s) failed: %s\n"),
@@ -367,7 +382,7 @@ relocate_targets_to_ag(
                }
 
                /* move to destination AG */
-               ret = relocate_file_to_ag(mnt, ipath->path, &xfd, dst_agno);
+               ret = relocate_file_to_ag(mnt, ipath, &xfd, dst_agno);
                xfd_close(&xfd);
 
                /*
index d4c71b7bb7f054c770d0400368f47eb8eab4ed2f..2c807aa678ec5b46658dd45da35e0e1bfb0b33d5 100644 (file)
@@ -43,9 +43,11 @@ struct inode_path {
  */
 #define UNLINKED_IPATH         ((struct inode_path *)1)
 
+struct inode_path *ipath_alloc(const char *path, const struct stat *st);
+
 int find_relocation_targets(xfs_agnumber_t agno);
-int relocate_file_to_ag(const char *mnt, const char *path, struct xfs_fd *xfd,
-                       xfs_agnumber_t agno);
+int relocate_file_to_ag(const char *mnt, struct inode_path *ipath,
+                       struct xfs_fd *xfd, xfs_agnumber_t agno);
 int resolve_target_paths(const char *mntpt);
 
 #endif /* XFS_SPACEMAN_RELOCATION_H_ */