Merge branch 'topic/misc' into for-linus
[firefly-linux-kernel-4.4.55.git] / fs / xfs / xfs_ialloc.c
index 9d884c127bb980c9c24b934113be222a4fc1093d..c7142a064c48d7b052d9ac0225f33562d0552386 100644 (file)
@@ -1203,6 +1203,63 @@ error0:
        return error;
 }
 
+STATIC int
+xfs_imap_lookup(
+       struct xfs_mount        *mp,
+       struct xfs_trans        *tp,
+       xfs_agnumber_t          agno,
+       xfs_agino_t             agino,
+       xfs_agblock_t           agbno,
+       xfs_agblock_t           *chunk_agbno,
+       xfs_agblock_t           *offset_agbno,
+       int                     flags)
+{
+       struct xfs_inobt_rec_incore rec;
+       struct xfs_btree_cur    *cur;
+       struct xfs_buf          *agbp;
+       xfs_agino_t             startino;
+       int                     error;
+       int                     i;
+
+       error = xfs_ialloc_read_agi(mp, tp, agno, &agbp);
+       if (error) {
+               xfs_fs_cmn_err(CE_ALERT, mp, "xfs_imap: "
+                               "xfs_ialloc_read_agi() returned "
+                               "error %d, agno %d",
+                               error, agno);
+               return error;
+       }
+
+       /*
+        * derive and lookup the exact inode record for the given agino. If the
+        * record cannot be found, then it's an invalid inode number and we
+        * should abort.
+        */
+       cur = xfs_inobt_init_cursor(mp, tp, agbp, agno);
+       startino = agino & ~(XFS_IALLOC_INODES(mp) - 1);
+       error = xfs_inobt_lookup(cur, startino, XFS_LOOKUP_EQ, &i);
+       if (!error) {
+               if (i)
+                       error = xfs_inobt_get_rec(cur, &rec, &i);
+               if (!error && i == 0)
+                       error = EINVAL;
+       }
+
+       xfs_trans_brelse(tp, agbp);
+       xfs_btree_del_cursor(cur, XFS_BTREE_NOERROR);
+       if (error)
+               return error;
+
+       /* for untrusted inodes check it is allocated first */
+       if ((flags & XFS_IGET_UNTRUSTED) &&
+           (rec.ir_free & XFS_INOBT_MASK(agino - rec.ir_startino)))
+               return EINVAL;
+
+       *chunk_agbno = XFS_AGINO_TO_AGBNO(mp, rec.ir_startino);
+       *offset_agbno = agbno - *chunk_agbno;
+       return 0;
+}
+
 /*
  * Return the location of the inode in imap, for mapping it into a buffer.
  */
@@ -1235,8 +1292,11 @@ xfs_imap(
        if (agno >= mp->m_sb.sb_agcount || agbno >= mp->m_sb.sb_agblocks ||
            ino != XFS_AGINO_TO_INO(mp, agno, agino)) {
 #ifdef DEBUG
-               /* no diagnostics for bulkstat, ino comes from userspace */
-               if (flags & XFS_IGET_BULKSTAT)
+               /*
+                * Don't output diagnostic information for untrusted inodes
+                * as they can be invalid without implying corruption.
+                */
+               if (flags & XFS_IGET_UNTRUSTED)
                        return XFS_ERROR(EINVAL);
                if (agno >= mp->m_sb.sb_agcount) {
                        xfs_fs_cmn_err(CE_ALERT, mp,
@@ -1263,6 +1323,23 @@ xfs_imap(
                return XFS_ERROR(EINVAL);
        }
 
+       blks_per_cluster = XFS_INODE_CLUSTER_SIZE(mp) >> mp->m_sb.sb_blocklog;
+
+       /*
+        * For bulkstat and handle lookups, we have an untrusted inode number
+        * that we have to verify is valid. We cannot do this just by reading
+        * the inode buffer as it may have been unlinked and removed leaving
+        * inodes in stale state on disk. Hence we have to do a btree lookup
+        * in all cases where an untrusted inode number is passed.
+        */
+       if (flags & XFS_IGET_UNTRUSTED) {
+               error = xfs_imap_lookup(mp, tp, agno, agino, agbno,
+                                       &chunk_agbno, &offset_agbno, flags);
+               if (error)
+                       return error;
+               goto out_map;
+       }
+
        /*
         * If the inode cluster size is the same as the blocksize or
         * smaller we get to the buffer by simple arithmetics.
@@ -1277,24 +1354,6 @@ xfs_imap(
                return 0;
        }
 
-       blks_per_cluster = XFS_INODE_CLUSTER_SIZE(mp) >> mp->m_sb.sb_blocklog;
-
-       /*
-        * If we get a block number passed from bulkstat we can use it to
-        * find the buffer easily.
-        */
-       if (imap->im_blkno) {
-               offset = XFS_INO_TO_OFFSET(mp, ino);
-               ASSERT(offset < mp->m_sb.sb_inopblock);
-
-               cluster_agbno = xfs_daddr_to_agbno(mp, imap->im_blkno);
-               offset += (agbno - cluster_agbno) * mp->m_sb.sb_inopblock;
-
-               imap->im_len = XFS_FSB_TO_BB(mp, blks_per_cluster);
-               imap->im_boffset = (ushort)(offset << mp->m_sb.sb_inodelog);
-               return 0;
-       }
-
        /*
         * If the inode chunks are aligned then use simple maths to
         * find the location. Otherwise we have to do a btree
@@ -1304,50 +1363,13 @@ xfs_imap(
                offset_agbno = agbno & mp->m_inoalign_mask;
                chunk_agbno = agbno - offset_agbno;
        } else {
-               xfs_btree_cur_t *cur;   /* inode btree cursor */
-               xfs_inobt_rec_incore_t chunk_rec;
-               xfs_buf_t       *agbp;  /* agi buffer */
-               int             i;      /* temp state */
-
-               error = xfs_ialloc_read_agi(mp, tp, agno, &agbp);
-               if (error) {
-                       xfs_fs_cmn_err(CE_ALERT, mp, "xfs_imap: "
-                                       "xfs_ialloc_read_agi() returned "
-                                       "error %d, agno %d",
-                                       error, agno);
-                       return error;
-               }
-
-               cur = xfs_inobt_init_cursor(mp, tp, agbp, agno);
-               error = xfs_inobt_lookup(cur, agino, XFS_LOOKUP_LE, &i);
-               if (error) {
-                       xfs_fs_cmn_err(CE_ALERT, mp, "xfs_imap: "
-                                       "xfs_inobt_lookup() failed");
-                       goto error0;
-               }
-
-               error = xfs_inobt_get_rec(cur, &chunk_rec, &i);
-               if (error) {
-                       xfs_fs_cmn_err(CE_ALERT, mp, "xfs_imap: "
-                                       "xfs_inobt_get_rec() failed");
-                       goto error0;
-               }
-               if (i == 0) {
-#ifdef DEBUG
-                       xfs_fs_cmn_err(CE_ALERT, mp, "xfs_imap: "
-                                       "xfs_inobt_get_rec() failed");
-#endif /* DEBUG */
-                       error = XFS_ERROR(EINVAL);
-               }
- error0:
-               xfs_trans_brelse(tp, agbp);
-               xfs_btree_del_cursor(cur, XFS_BTREE_NOERROR);
+               error = xfs_imap_lookup(mp, tp, agno, agino, agbno,
+                                       &chunk_agbno, &offset_agbno, flags);
                if (error)
                        return error;
-               chunk_agbno = XFS_AGINO_TO_AGBNO(mp, chunk_rec.ir_startino);
-               offset_agbno = agbno - chunk_agbno;
        }
 
+out_map:
        ASSERT(agbno >= chunk_agbno);
        cluster_agbno = chunk_agbno +
                ((offset_agbno / blks_per_cluster) * blks_per_cluster);