]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
repair: scan and track sparse inode chunks correctly
authorBrian Foster <bfoster@redhat.com>
Tue, 23 Jun 2015 05:08:47 +0000 (15:08 +1000)
committerDave Chinner <david@fromorbit.com>
Tue, 23 Jun 2015 05:08:47 +0000 (15:08 +1000)
Phase 2 of xfs_repair scans the on-disk inobt and creates in-core
records for all inodes in the fs. This also involves marking
free/allocated state of all inodes, internal record verification and
block state management for the inode chunks tracked by inode records.
Various parts of the inobt scan mechanism assume fully allocated inode
records and thus lead to spurious errors when sparse inode records are
encountered.

Update the inobt scan to detect and handle sparse inode records
correctly. Do not set the allocation state of blocks in sparse inode
regions as these blocks do not belong to the record. Do not account
sparse inodes against the ir_freecount as these inodes do not exist and
are not available for allocation by the fs. Finally, track the sparse
status of each individual inode in the in-core inode records for future
reference.

Signed-off-by: Brian Foster <bfoster@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
include/libxfs.h
repair/incore.h
repair/incore_ino.c
repair/scan.c

index 6a59cc02412143978ace8a816c9f029a8384643a..e12cd5ab59fd925f28b0018b95ebb89e3faf4745 100644 (file)
@@ -183,6 +183,22 @@ extern unsigned long       libxfs_physmem(void);   /* in kilobytes */
 #define XFS_INOBT_IS_FREE_DISK(rp,i)           \
                        ((be64_to_cpu((rp)->ir_free) & XFS_INOBT_MASK(i)) != 0)
 
+static inline bool
+xfs_inobt_is_sparse_disk(
+       struct xfs_inobt_rec    *rp,
+       int                     offset)
+{
+       int                     spshift;
+       uint16_t                holemask;
+
+       holemask = be16_to_cpu(rp->ir_u.sp.ir_holemask);
+       spshift = offset / XFS_INODES_PER_HOLEMASK_BIT;
+       if ((1 << spshift) & holemask)
+               return true;
+
+       return false;
+}
+
 static inline void
 libxfs_bmbt_disk_get_all(
        struct xfs_bmbt_rec     *rp,
index ba819b4eca5490388be9a2b85a236d0e1fb4f023..d4e44a7a6b10d6fd79a9069224bdc375ce35cf2f 100644 (file)
@@ -285,6 +285,7 @@ typedef struct ino_tree_node  {
        avlnode_t               avl_node;
        xfs_agino_t             ino_startnum;   /* starting inode # */
        xfs_inofree_t           ir_free;        /* inode free bit mask */
+       __uint64_t              ir_sparse;      /* sparse inode bitmask */
        __uint64_t              ino_confirmed;  /* confirmed bitmask */
        __uint64_t              ino_isa_dir;    /* bit == 1 if a directory */
        __uint8_t               nlink_size;
@@ -476,6 +477,19 @@ static inline int is_inode_free(struct ino_tree_node *irec, int offset)
        return (irec->ir_free & XFS_INOBT_MASK(offset)) != 0;
 }
 
+/*
+ * set/test is inode sparse (not physically allocated)
+ */
+static inline void set_inode_sparse(struct ino_tree_node *irec, int offset)
+{
+       irec->ir_sparse |= XFS_INOBT_MASK(offset);
+}
+
+static inline bool is_inode_sparse(struct ino_tree_node *irec, int offset)
+{
+       return irec->ir_sparse & XFS_INOBT_MASK(offset);
+}
+
 /*
  * add_inode_reached() is set on inode I only if I has been reached
  * by an inode P claiming to be the parent and if I is a directory,
index 95026481c72c570c30e57d03d4f6d261029bb726..cda6c2bf05932e0170665eeb9bbe38b077d557fc 100644 (file)
@@ -258,6 +258,7 @@ alloc_ino_node(
        irec->ino_confirmed = 0;
        irec->ino_isa_dir = 0;
        irec->ir_free = (xfs_inofree_t) - 1;
+       irec->ir_sparse = 0;
        irec->ino_un.ex_data = NULL;
        irec->nlink_size = sizeof(__uint8_t);
        irec->disk_nlinks.un8 = alloc_nlink_array(irec->nlink_size);
index 4069f230e4ed7b152720cedbdfd8902b5d8d8c4a..63663d059b5a42a85c903c83885d9148e0ead1e5 100644 (file)
@@ -753,6 +753,17 @@ _("%s freespace btree block claimed (state %d), agno %d, bno %d, suspect %d\n"),
        }
 }
 
+static bool
+ino_issparse(
+       struct xfs_inobt_rec    *rp,
+       int                     offset)
+{
+       if (!xfs_sb_version_hassparseinodes(&mp->m_sb))
+               return false;
+
+       return xfs_inobt_is_sparse_disk(rp, offset);
+}
+
 static int
 scan_single_ino_chunk(
        xfs_agnumber_t          agno,
@@ -766,7 +777,8 @@ scan_single_ino_chunk(
        int                     nfree;
        int                     off;
        int                     state;
-       ino_tree_node_t         *ino_rec, *first_rec, *last_rec;
+       ino_tree_node_t         *ino_rec = NULL;
+       ino_tree_node_t         *first_rec, *last_rec;
        int                     freecount;
 
        ino = be32_to_cpu(rp->ir_startino);
@@ -832,8 +844,12 @@ _("bad ending inode # (%" PRIu64 " (0x%x 0x%zx)) in ino rec, skipping rec\n"),
                for (j = 0;
                     j < XFS_INODES_PER_CHUNK;
                     j += mp->m_sb.sb_inopblock)  {
-                       agbno = XFS_AGINO_TO_AGBNO(mp, ino + j);
 
+                       /* inodes in sparse chunks don't use blocks */
+                       if (ino_issparse(rp, j))
+                               continue;
+
+                       agbno = XFS_AGINO_TO_AGBNO(mp, ino + j);
                        state = get_bmap(agno, agbno);
                        if (state == XR_E_UNKNOWN)  {
                                set_bmap(agno, agbno, XR_E_INO);
@@ -878,8 +894,6 @@ _("inode rec for ino %" PRIu64 " (%d/%d) overlaps existing rec (start %d/%d)\n")
                        return suspect;
        }
 
-       nfree = 0;
-
        /*
         * now mark all the inodes as existing and free or used.
         * if the tree is suspect, put them into the uncertain
@@ -887,14 +901,12 @@ _("inode rec for ino %" PRIu64 " (%d/%d) overlaps existing rec (start %d/%d)\n")
         */
        if (!suspect)  {
                if (XFS_INOBT_IS_FREE_DISK(rp, 0)) {
-                       nfree++;
                        ino_rec = set_inode_free_alloc(mp, agno, ino);
                } else  {
                        ino_rec = set_inode_used_alloc(mp, agno, ino);
                }
                for (j = 1; j < XFS_INODES_PER_CHUNK; j++) {
                        if (XFS_INOBT_IS_FREE_DISK(rp, j)) {
-                               nfree++;
                                set_inode_free(ino_rec, j);
                        } else  {
                                set_inode_used(ino_rec, j);
@@ -903,7 +915,6 @@ _("inode rec for ino %" PRIu64 " (%d/%d) overlaps existing rec (start %d/%d)\n")
        } else  {
                for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
                        if (XFS_INOBT_IS_FREE_DISK(rp, j)) {
-                               nfree++;
                                add_aginode_uncertain(mp, agno, ino + j, 1);
                        } else  {
                                add_aginode_uncertain(mp, agno, ino + j, 0);
@@ -911,6 +922,29 @@ _("inode rec for ino %" PRIu64 " (%d/%d) overlaps existing rec (start %d/%d)\n")
                }
        }
 
+       /*
+        * Mark sparse inodes as such in the in-core tree. Verify that sparse
+        * inodes are free and that freecount is consistent with the free mask.
+        */
+       nfree = 0;
+       for (j = 0; j < XFS_INODES_PER_CHUNK; j++) {
+               if (ino_issparse(rp, j)) {
+                       if (!suspect && !XFS_INOBT_IS_FREE_DISK(rp, j)) {
+                               do_warn(
+_("ir_holemask/ir_free mismatch, inode chunk %d/%u, holemask 0x%x free 0x%llx\n"),
+                                       agno, ino,
+                                       be16_to_cpu(rp->ir_u.sp.ir_holemask),
+                                       be64_to_cpu(rp->ir_free));
+                               suspect++;
+                       }
+                       if (!suspect && ino_rec)
+                               set_inode_sparse(ino_rec, j);
+               } else if (XFS_INOBT_IS_FREE_DISK(rp, j)) {
+                       /* freecount only tracks non-sparse inos */
+                       nfree++;
+               }
+       }
+
        if (nfree != freecount) {
                do_warn(
 _("ir_freecount/free mismatch, inode chunk %d/%u, freecount %d nfree %d\n"),