]> git-server-git.apps.pok.os.sepia.ceph.com Git - xfsprogs-dev.git/commitdiff
xfs_healer: use statmount to find moved filesystems even faster
authorDarrick J. Wong <djwong@kernel.org>
Thu, 5 Mar 2026 18:47:54 +0000 (10:47 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Thu, 9 Apr 2026 22:30:17 +0000 (15:30 -0700)
As noted in the previous patch, it's possible that a mounted filesystem
can move mountpoints between the time of the initial mount (at which
point xfs_healer starts) and when it actually wants to start a repair.
The previous patch fixed that problem by using getmntent to walk
/proc/self/mounts to see if it finds a mount with the same "source"
name, aka data device.

However, this is really slow if there are a lot of filesystems because
we end up wading through a lot of irrelevant information.  However,
statmount() can help us here because as of Linux 7.0 we can open the
passed-in path at startup, call statmount() on it to retrieve the
mnt_id, and then call it again later with that same mnt_id to find the
mountpoint.  Luckily xfs_healthmon didn't get merged until 7.0 so it's
more or less guaranteed to be there if XFS_IOC_HEALTH_MONITOR succeeds.

Obviously if this doesn't work, we can fall back to the slow walk.

This statmount code enables xfs_healer to find a filesystem that has
had its mountpoint moved to a different place in the directory tree
without the use of bind mounts and without needing to walk the entire
mount list:

# mount -t tmpfs urk /mnt
# mount --make-rprivate /mnt
# mkdir -p /mnt/a /mnt/b
# mount /dev/sda /mnt/a
# mount --move /mnt/a /mnt/b

The key here is that the struct mount object is moved, and no new ones
are created.  Therefore, the original mnt_id is still usable.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
healer/weakhandle.c
healer/xfs_healer.c
healer/xfs_healer.h

index 5df5207514e38e4cafcfc9d7ed2ed377eceff028..358c553f883f3d72d57463ab57c5c495e7475482 100644 (file)
@@ -14,6 +14,7 @@
 #include "libfrog/getparents.h"
 #include "libfrog/paths.h"
 #include "libfrog/systemd.h"
+#include "libfrog/statmount.h"
 #include "xfs_healer.h"
 
 struct weakhandle {
@@ -23,6 +24,9 @@ struct weakhandle {
        /* Shared reference to the getmntent fsname for reconnecting */
        const char              *fsname;
 
+       /* Mount id for faster reconnecting */
+       uint64_t                mnt_id;
+
        /* handle to root dir */
        void                    *hanp;
        size_t                  hlen;
@@ -33,6 +37,7 @@ int
 weakhandle_alloc(
        int                     fd,
        const char              *mountpoint,
+       uint64_t                mnt_id,
        const char              *fsname,
        struct weakhandle       **whp)
 {
@@ -51,6 +56,7 @@ weakhandle_alloc(
                return -1;
 
        wh->mntpoint = mountpoint;
+       wh->mnt_id = mnt_id;
        wh->fsname = fsname;
 
        ret = fd_to_handle(fd, &wh->hanp, &wh->hlen);
@@ -112,6 +118,9 @@ weakhandle_reopen(
        struct weakhandle       *wh,
        int                     *fd)
 {
+       const size_t            smbuf_size =
+               libfrog_statmount_sizeof(PATH_MAX);
+       struct statmount        *smbuf = alloca(smbuf_size);
        FILE                    *mtab;
        struct mntent           *mnt;
        int                     ret;
@@ -121,6 +130,21 @@ weakhandle_reopen(
        if (!ret)
                return 0;
 
+       /*
+        * The original mountpoint didn't work, which means the mount might
+        * have been moved.  Look up the mountpoint for the mount id that we
+        * captured earlier, which is a quick lookup if there are many mounts.
+        * Note that @ret is nonzero here.
+        */
+       ret = libfrog_statmount(wh->mnt_id, DEFAULT_MOUNTNS_FD,
+                       STATMOUNT_MNT_POINT, smbuf, smbuf_size);
+       if (ret || !(smbuf->mask & STATMOUNT_MNT_POINT))
+               goto fallback;
+       ret = weakhandle_reopen_from(wh, smbuf->str + smbuf->mnt_point, fd);
+       if (!ret)
+               return 0;
+
+fallback:
        /*
         * That didn't work, so now walk /proc/mounts to find a mount with the
         * same fsname (aka xfs data device path) as when we started.
index d885eb0a50826b917fd69c9a4bcd8328db0b0e0c..36e52a71bebfa7ecc36fb8f2c3ccbef0682f436f 100644 (file)
@@ -15,6 +15,7 @@
 #include "libfrog/workqueue.h"
 #include "libfrog/systemd.h"
 #include "libfrog/fsproperties.h"
+#include "libfrog/statmount.h"
 #include "xfs_healer.h"
 
 /* Program name; needed for libfrog error reports. */
@@ -169,11 +170,45 @@ try_capture_fsinfo(
 {
        struct mntent           *mnt;
        FILE                    *mtp;
-       char                    rpath[PATH_MAX], rmnt_dir[PATH_MAX];
+       const size_t            smbuf_size =
+               libfrog_statmount_sizeof(PATH_MAX + 128);
+       struct statmount        *smbuf = alloca(smbuf_size);
+       char                    *rmnt_dir = smbuf->str;
+       char                    rpath[PATH_MAX];
+       int                     ret;
 
        if (!realpath(ctx->mntpoint, rpath))
                return -1;
 
+       /*
+        * In Linux 7.0 we can do statmount on an open file, which means that
+        * we can capture the mnt_id, mount point, and fsname, which can help
+        * us find a mount --move'd elsewhere in the directory tree.
+        */
+       ret = libfrog_fstatmount(ctx->mnt.fd, STATMOUNT_MNT_POINT, smbuf,
+                       smbuf_size);
+       if (ret || !(smbuf->mask & STATMOUNT_MNT_POINT))
+               goto fallback;
+       if (strcmp(rpath, smbuf->str + smbuf->mnt_point))
+               goto fallback;
+
+       ret = libfrog_fstatmount(ctx->mnt.fd,
+                       STATMOUNT_SB_SOURCE | STATMOUNT_MNT_BASIC,
+                       smbuf, smbuf_size);
+       if (ret || !(smbuf->mask & STATMOUNT_SB_SOURCE))
+               goto fallback;
+
+       ctx->fsname = strdup(smbuf->str + smbuf->sb_source);
+       if (!ctx->fsname)
+               return -1;
+       ctx->mnt_id = smbuf->mnt_id;
+       return 0;
+
+fallback:
+       /*
+        * If statmount isn't available for whatever reason, fall back to
+        * walking the mount table via getmntent.
+        */
        mtp = setmntent(_PATH_PROC_MOUNTS, "r");
        if (mtp == NULL)
                return -1;
@@ -350,7 +385,7 @@ setup_monitor(
         * paths for logging.
         */
        if (ctx->want_repair || healer_has_parent(ctx)) {
-               ret = weakhandle_alloc(ctx->mnt.fd, ctx->mntpoint,
+               ret = weakhandle_alloc(ctx->mnt.fd, ctx->mntpoint, ctx->mnt_id,
                                ctx->fsname, &ctx->wh);
                if (ret) {
                        fprintf(stderr, "%s: %s: %s\n", ctx->mntpoint,
index e1370323bbd66a5f6245e47c98390f0842afd9d1..96e146f266629aa4464e771086e66ebd581d1677 100644 (file)
@@ -39,6 +39,9 @@ struct healer_ctx {
        /* Shared reference to the getmntent fsname for reconnecting */
        const char              *fsname;
 
+       /* Mount id for faster reconnecting */
+       uint64_t                mnt_id;
+
        /* weak file handle so we can reattach to filesystem */
        struct weakhandle       *wh;
 
@@ -75,8 +78,8 @@ bool healer_can_repair(struct healer_ctx *ctx);
 void run_full_repair(struct healer_ctx *ctx);
 
 /* weakhandle.c */
-int weakhandle_alloc(int fd, const char *mountpoint, const char *fsname,
-               struct weakhandle **whp);
+int weakhandle_alloc(int fd, const char *mountpoint, uint64_t mnt_id,
+               const char *fsname, struct weakhandle **whp);
 int weakhandle_reopen(struct weakhandle *wh, int *fd);
 void weakhandle_free(struct weakhandle **whp);
 int weakhandle_getpath_for(struct weakhandle *wh, uint64_t ino, uint32_t gen,