From 8fe59505226c247e1129d26b5a4f4b89f4c9ac6f Mon Sep 17 00:00:00 2001 From: Dave Chinner Date: Wed, 7 Aug 2024 15:54:54 -0700 Subject: [PATCH] spaceman: move inodes with hardlinks 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 Reviewed-by: Darrick J. Wong Signed-off-by: Darrick J. Wong --- spaceman/find_owner.c | 13 +---- spaceman/move_inode.c | 119 ++++++++++++++++++++++++++++++++++++++---- spaceman/relocation.c | 35 +++++++++---- spaceman/relocation.h | 6 ++- 4 files changed, 140 insertions(+), 33 deletions(-) diff --git a/spaceman/find_owner.c b/spaceman/find_owner.c index 8e9314553..1984d0ee7 100644 --- a/spaceman/find_owner.c +++ b/spaceman/find_owner.c @@ -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 diff --git a/spaceman/move_inode.c b/spaceman/move_inode.c index ab3c12f5d..3a1829295 100644 --- a/spaceman/move_inode.c +++ b/spaceman/move_inode.c @@ -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; diff --git a/spaceman/relocation.c b/spaceman/relocation.c index 7b125cc0a..b09602721 100644 --- a/spaceman/relocation.c +++ b/spaceman/relocation.c @@ -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); /* diff --git a/spaceman/relocation.h b/spaceman/relocation.h index d4c71b7bb..2c807aa67 100644 --- a/spaceman/relocation.h +++ b/spaceman/relocation.h @@ -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_ */ -- 2.50.1