xfs: add CRC checks to the AGFL
[linux-2.6-block.git] / fs / xfs / xfs_alloc.c
index c34f799aa92e76f987733807e8935cdeea64bea2..40d3d576f473056532992b18c4cb6403615558bd 100644 (file)
@@ -432,53 +432,84 @@ xfs_alloc_fixup_trees(
        return 0;
 }
 
-static void
+static bool
 xfs_agfl_verify(
        struct xfs_buf  *bp)
 {
-#ifdef WHEN_CRCS_COME_ALONG
-       /*
-        * we cannot actually do any verification of the AGFL because mkfs does
-        * not initialise the AGFL to zero or NULL. Hence the only valid part of
-        * the AGFL is what the AGF says is active. We can't get to the AGF, so
-        * we can't verify just those entries are valid.
-        *
-        * This problem goes away when the CRC format change comes along as that
-        * requires the AGFL to be initialised by mkfs. At that point, we can
-        * verify the blocks in the agfl -active or not- lie within the bounds
-        * of the AG. Until then, just leave this check ifdef'd out.
-        */
        struct xfs_mount *mp = bp->b_target->bt_mount;
        struct xfs_agfl *agfl = XFS_BUF_TO_AGFL(bp);
-       int             agfl_ok = 1;
-
        int             i;
 
+       if (!uuid_equal(&agfl->agfl_uuid, &mp->m_sb.sb_uuid))
+               return false;
+       if (be32_to_cpu(agfl->agfl_magicnum) != XFS_AGFL_MAGIC)
+               return false;
+       /*
+        * during growfs operations, the perag is not fully initialised,
+        * so we can't use it for any useful checking. growfs ensures we can't
+        * use it by using uncached buffers that don't have the perag attached
+        * so we can detect and avoid this problem.
+        */
+       if (bp->b_pag && be32_to_cpu(agfl->agfl_seqno) != bp->b_pag->pag_agno)
+               return false;
+
        for (i = 0; i < XFS_AGFL_SIZE(mp); i++) {
-               if (be32_to_cpu(agfl->agfl_bno[i]) == NULLAGBLOCK ||
+               if (be32_to_cpu(agfl->agfl_bno[i]) != NULLAGBLOCK &&
                    be32_to_cpu(agfl->agfl_bno[i]) >= mp->m_sb.sb_agblocks)
-                       agfl_ok = 0;
+                       return false;
        }
+       return true;
+}
+
+static void
+xfs_agfl_read_verify(
+       struct xfs_buf  *bp)
+{
+       struct xfs_mount *mp = bp->b_target->bt_mount;
+       int             agfl_ok = 1;
+
+       /*
+        * There is no verification of non-crc AGFLs because mkfs does not
+        * initialise the AGFL to zero or NULL. Hence the only valid part of the
+        * AGFL is what the AGF says is active. We can't get to the AGF, so we
+        * can't verify just those entries are valid.
+        */
+       if (!xfs_sb_version_hascrc(&mp->m_sb))
+               return;
+
+       agfl_ok = xfs_verify_cksum(bp->b_addr, BBTOB(bp->b_length),
+                                  offsetof(struct xfs_agfl, agfl_crc));
+
+       agfl_ok = agfl_ok && xfs_agfl_verify(bp);
 
        if (!agfl_ok) {
-               XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, agfl);
+               XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, bp->b_addr);
                xfs_buf_ioerror(bp, EFSCORRUPTED);
        }
-#endif
 }
 
 static void
 xfs_agfl_write_verify(
        struct xfs_buf  *bp)
 {
-       xfs_agfl_verify(bp);
-}
+       struct xfs_mount *mp = bp->b_target->bt_mount;
+       struct xfs_buf_log_item *bip = bp->b_fspriv;
 
-static void
-xfs_agfl_read_verify(
-       struct xfs_buf  *bp)
-{
-       xfs_agfl_verify(bp);
+       /* no verification of non-crc AGFLs */
+       if (!xfs_sb_version_hascrc(&mp->m_sb))
+               return;
+
+       if (!xfs_agfl_verify(bp)) {
+               XFS_CORRUPTION_ERROR(__func__, XFS_ERRLEVEL_LOW, mp, bp->b_addr);
+               xfs_buf_ioerror(bp, EFSCORRUPTED);
+               return;
+       }
+
+       if (bip)
+               XFS_BUF_TO_AGFL(bp)->agfl_lsn = cpu_to_be64(bip->bli_item.li_lsn);
+
+       xfs_update_cksum(bp->b_addr, BBTOB(bp->b_length),
+                        offsetof(struct xfs_agfl, agfl_crc));
 }
 
 const struct xfs_buf_ops xfs_agfl_buf_ops = {
@@ -1984,18 +2015,18 @@ xfs_alloc_get_freelist(
        int             btreeblk) /* destination is a AGF btree */
 {
        xfs_agf_t       *agf;   /* a.g. freespace structure */
-       xfs_agfl_t      *agfl;  /* a.g. freelist structure */
        xfs_buf_t       *agflbp;/* buffer for a.g. freelist structure */
        xfs_agblock_t   bno;    /* block number returned */
+       __be32          *agfl_bno;
        int             error;
        int             logflags;
-       xfs_mount_t     *mp;    /* mount structure */
+       xfs_mount_t     *mp = tp->t_mountp;
        xfs_perag_t     *pag;   /* per allocation group data */
 
-       agf = XFS_BUF_TO_AGF(agbp);
        /*
         * Freelist is empty, give up.
         */
+       agf = XFS_BUF_TO_AGF(agbp);
        if (!agf->agf_flcount) {
                *bnop = NULLAGBLOCK;
                return 0;
@@ -2003,15 +2034,17 @@ xfs_alloc_get_freelist(
        /*
         * Read the array of free blocks.
         */
-       mp = tp->t_mountp;
-       if ((error = xfs_alloc_read_agfl(mp, tp,
-                       be32_to_cpu(agf->agf_seqno), &agflbp)))
+       error = xfs_alloc_read_agfl(mp, tp, be32_to_cpu(agf->agf_seqno),
+                                   &agflbp);
+       if (error)
                return error;
-       agfl = XFS_BUF_TO_AGFL(agflbp);
+
+
        /*
         * Get the block number and update the data structures.
         */
-       bno = be32_to_cpu(agfl->agfl_bno[be32_to_cpu(agf->agf_flfirst)]);
+       agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, agflbp);
+       bno = be32_to_cpu(agfl_bno[be32_to_cpu(agf->agf_flfirst)]);
        be32_add_cpu(&agf->agf_flfirst, 1);
        xfs_trans_brelse(tp, agflbp);
        if (be32_to_cpu(agf->agf_flfirst) == XFS_AGFL_SIZE(mp))
@@ -2104,12 +2137,13 @@ xfs_alloc_put_freelist(
        int                     btreeblk) /* block came from a AGF btree */
 {
        xfs_agf_t               *agf;   /* a.g. freespace structure */
-       xfs_agfl_t              *agfl;  /* a.g. free block array */
        __be32                  *blockp;/* pointer to array entry */
        int                     error;
        int                     logflags;
        xfs_mount_t             *mp;    /* mount structure */
        xfs_perag_t             *pag;   /* per allocation group data */
+       __be32                  *agfl_bno;
+       int                     startoff;
 
        agf = XFS_BUF_TO_AGF(agbp);
        mp = tp->t_mountp;
@@ -2117,7 +2151,6 @@ xfs_alloc_put_freelist(
        if (!agflbp && (error = xfs_alloc_read_agfl(mp, tp,
                        be32_to_cpu(agf->agf_seqno), &agflbp)))
                return error;
-       agfl = XFS_BUF_TO_AGFL(agflbp);
        be32_add_cpu(&agf->agf_fllast, 1);
        if (be32_to_cpu(agf->agf_fllast) == XFS_AGFL_SIZE(mp))
                agf->agf_fllast = 0;
@@ -2138,13 +2171,17 @@ xfs_alloc_put_freelist(
        xfs_alloc_log_agf(tp, agbp, logflags);
 
        ASSERT(be32_to_cpu(agf->agf_flcount) <= XFS_AGFL_SIZE(mp));
-       blockp = &agfl->agfl_bno[be32_to_cpu(agf->agf_fllast)];
+
+       agfl_bno = XFS_BUF_TO_AGFL_BNO(mp, agflbp);
+       blockp = &agfl_bno[be32_to_cpu(agf->agf_fllast)];
        *blockp = cpu_to_be32(bno);
+       startoff = (char *)blockp - (char *)agflbp->b_addr;
+
        xfs_alloc_log_agf(tp, agbp, logflags);
-       xfs_trans_log_buf(tp, agflbp,
-               (int)((xfs_caddr_t)blockp - (xfs_caddr_t)agfl),
-               (int)((xfs_caddr_t)blockp - (xfs_caddr_t)agfl +
-                       sizeof(xfs_agblock_t) - 1));
+
+       xfs_trans_buf_set_type(tp, agflbp, XFS_BLF_AGFL_BUF);
+       xfs_trans_log_buf(tp, agflbp, startoff,
+                         startoff + sizeof(xfs_agblock_t) - 1);
        return 0;
 }