xfs: check rt summary file geometry more thoroughly
authorDarrick J. Wong <djwong@kernel.org>
Fri, 15 Dec 2023 18:03:41 +0000 (10:03 -0800)
committerDarrick J. Wong <djwong@kernel.org>
Fri, 15 Dec 2023 18:03:41 +0000 (10:03 -0800)
I forgot that the xfs_mount tracks the size and number of levels in the
realtime summary file, and that the rt summary file can have more blocks
mapped to the data fork than m_rsumsize implies if growfsrt fails.

So.  Add to the rtsummary scrubber an explicit check that all the
summary geometry values are correct, then adjust the rtsummary i_size
checks to allow for the growfsrt failure case.  Finally, flag post-eof
blocks in the summary file.

While we're at it, split the extent map checking so that we only call
xfs_bmapi_read once per extent instead of once per rtsummary block.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
fs/xfs/scrub/rtsummary.c

index f94800a029f358e0a487a790db1b0ccc334b3ea8..b0d90426a5cb8d214c8cdf38a6f1da92fc926eb7 100644 (file)
  * (potentially large) amount of data in pageable memory.
  */
 
+struct xchk_rtsummary {
+       struct xfs_rtalloc_args args;
+
+       uint64_t                rextents;
+       uint64_t                rbmblocks;
+       uint64_t                rsumsize;
+       unsigned int            rsumlevels;
+
+       /* Memory buffer for the summary comparison. */
+       union xfs_suminfo_raw   words[];
+};
+
 /* Set us up to check the rtsummary file. */
 int
 xchk_setup_rtsummary(
@@ -38,8 +50,15 @@ xchk_setup_rtsummary(
 {
        struct xfs_mount        *mp = sc->mp;
        char                    *descr;
+       struct xchk_rtsummary   *rts;
        int                     error;
 
+       rts = kvzalloc(struct_size(rts, words, mp->m_blockwsize),
+                       XCHK_GFP_FLAGS);
+       if (!rts)
+               return -ENOMEM;
+       sc->buf = rts;
+
        /*
         * Create an xfile to construct a new rtsummary file.  The xfile allows
         * us to avoid pinning kernel memory for this purpose.
@@ -54,11 +73,6 @@ xchk_setup_rtsummary(
        if (error)
                return error;
 
-       /* Allocate a memory buffer for the summary comparison. */
-       sc->buf = kvmalloc(mp->m_sb.sb_blocksize, XCHK_GFP_FLAGS);
-       if (!sc->buf)
-               return -ENOMEM;
-
        error = xchk_install_live_inode(sc, mp->m_rsumip);
        if (error)
                return error;
@@ -75,13 +89,29 @@ xchk_setup_rtsummary(
         */
        xfs_ilock(mp->m_rbmip, XFS_ILOCK_SHARED | XFS_ILOCK_RTBITMAP);
        xchk_ilock(sc, XFS_ILOCK_EXCL | XFS_ILOCK_RTSUM);
+
+       /*
+        * Now that we've locked the rtbitmap and rtsummary, we can't race with
+        * growfsrt trying to expand the summary or change the size of the rt
+        * volume.  Hence it is safe to compute and check the geometry values.
+        */
+       if (mp->m_sb.sb_rblocks) {
+               xfs_filblks_t   rsumblocks;
+               int             rextslog;
+
+               rts->rextents = xfs_rtb_to_rtx(mp, mp->m_sb.sb_rblocks);
+               rextslog = xfs_compute_rextslog(rts->rextents);
+               rts->rsumlevels = rextslog + 1;
+               rts->rbmblocks = xfs_rtbitmap_blockcount(mp, rts->rextents);
+               rsumblocks = xfs_rtsummary_blockcount(mp, rts->rsumlevels,
+                               rts->rbmblocks);
+               rts->rsumsize = XFS_FSB_TO_B(mp, rsumblocks);
+       }
        return 0;
 }
 
 /* Helper functions to record suminfo words in an xfile. */
 
-typedef unsigned int xchk_rtsumoff_t;
-
 static inline int
 xfsum_load(
        struct xfs_scrub        *sc,
@@ -192,19 +222,29 @@ STATIC int
 xchk_rtsum_compare(
        struct xfs_scrub        *sc)
 {
-       struct xfs_rtalloc_args args = {
-               .mp             = sc->mp,
-               .tp             = sc->tp,
-       };
-       struct xfs_mount        *mp = sc->mp;
        struct xfs_bmbt_irec    map;
-       xfs_fileoff_t           off;
-       xchk_rtsumoff_t         sumoff = 0;
-       int                     nmap;
+       struct xfs_iext_cursor  icur;
+
+       struct xfs_mount        *mp = sc->mp;
+       struct xfs_inode        *ip = sc->ip;
+       struct xchk_rtsummary   *rts = sc->buf;
+       xfs_fileoff_t           off = 0;
+       xfs_fileoff_t           endoff;
+       xfs_rtsumoff_t          sumoff = 0;
+       int                     error = 0;
 
-       for (off = 0; off < XFS_B_TO_FSB(mp, mp->m_rsumsize); off++) {
-               union xfs_suminfo_raw *ondisk_info;
-               int             error = 0;
+       rts->args.mp = sc->mp;
+       rts->args.tp = sc->tp;
+
+       /* Mappings may not cross or lie beyond EOF. */
+       endoff = XFS_B_TO_FSB(mp, ip->i_disk_size);
+       if (xfs_iext_lookup_extent(ip, &ip->i_df, endoff, &icur, &map)) {
+               xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, endoff);
+               return 0;
+       }
+
+       while (off < endoff) {
+               int             nmap = 1;
 
                if (xchk_should_terminate(sc, &error))
                        return error;
@@ -212,8 +252,7 @@ xchk_rtsum_compare(
                        return 0;
 
                /* Make sure we have a written extent. */
-               nmap = 1;
-               error = xfs_bmapi_read(mp->m_rsumip, off, 1, &map, &nmap,
+               error = xfs_bmapi_read(ip, off, endoff - off, &map, &nmap,
                                XFS_DATA_FORK);
                if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, off, &error))
                        return error;
@@ -223,24 +262,33 @@ xchk_rtsum_compare(
                        return 0;
                }
 
+               off += map.br_blockcount;
+       }
+
+       for (off = 0; off < endoff; off++) {
+               union xfs_suminfo_raw   *ondisk_info;
+
                /* Read a block's worth of ondisk rtsummary file. */
-               error = xfs_rtsummary_read_buf(&args, off);
+               error = xfs_rtsummary_read_buf(&rts->args, off);
                if (!xchk_fblock_process_error(sc, XFS_DATA_FORK, off, &error))
                        return error;
 
                /* Read a block's worth of computed rtsummary file. */
-               error = xfsum_copyout(sc, sumoff, sc->buf, mp->m_blockwsize);
+               error = xfsum_copyout(sc, sumoff, rts->words, mp->m_blockwsize);
                if (error) {
-                       xfs_rtbuf_cache_relse(&args);
+                       xfs_rtbuf_cache_relse(&rts->args);
                        return error;
                }
 
-               ondisk_info = xfs_rsumblock_infoptr(&args, 0);
-               if (memcmp(ondisk_info, sc->buf,
-                                       mp->m_blockwsize << XFS_WORDLOG) != 0)
+               ondisk_info = xfs_rsumblock_infoptr(&rts->args, 0);
+               if (memcmp(ondisk_info, rts->words,
+                                       mp->m_blockwsize << XFS_WORDLOG) != 0) {
                        xchk_fblock_set_corrupt(sc, XFS_DATA_FORK, off);
+                       xfs_rtbuf_cache_relse(&rts->args);
+                       return error;
+               }
 
-               xfs_rtbuf_cache_relse(&args);
+               xfs_rtbuf_cache_relse(&rts->args);
                sumoff += mp->m_blockwsize;
        }
 
@@ -253,8 +301,43 @@ xchk_rtsummary(
        struct xfs_scrub        *sc)
 {
        struct xfs_mount        *mp = sc->mp;
+       struct xchk_rtsummary   *rts = sc->buf;
        int                     error = 0;
 
+       /* Is sb_rextents correct? */
+       if (mp->m_sb.sb_rextents != rts->rextents) {
+               xchk_ino_set_corrupt(sc, mp->m_rbmip->i_ino);
+               goto out_rbm;
+       }
+
+       /* Is m_rsumlevels correct? */
+       if (mp->m_rsumlevels != rts->rsumlevels) {
+               xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino);
+               goto out_rbm;
+       }
+
+       /* Is m_rsumsize correct? */
+       if (mp->m_rsumsize != rts->rsumsize) {
+               xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino);
+               goto out_rbm;
+       }
+
+       /* The summary file length must be aligned to an fsblock. */
+       if (mp->m_rsumip->i_disk_size & mp->m_blockmask) {
+               xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino);
+               goto out_rbm;
+       }
+
+       /*
+        * Is the summary file itself large enough to handle the rt volume?
+        * growfsrt expands the summary file before updating sb_rextents, so
+        * the file can be larger than rsumsize.
+        */
+       if (mp->m_rsumip->i_disk_size < rts->rsumsize) {
+               xchk_ino_set_corrupt(sc, mp->m_rsumip->i_ino);
+               goto out_rbm;
+       }
+
        /* Invoke the fork scrubber. */
        error = xchk_metadata_inode_forks(sc);
        if (error || (sc->sm->sm_flags & XFS_SCRUB_OFLAG_CORRUPT))