xfs: bulkstat chunk-formatter has issues
[pandora-kernel.git] / fs / xfs / xfs_itable.c
index f1deb96..7ea2b11 100644 (file)
@@ -262,75 +262,70 @@ xfs_bulkstat_grab_ichunk(
 
 #define XFS_BULKSTAT_UBLEFT(ubleft)    ((ubleft) >= statstruct_size)
 
+struct xfs_bulkstat_agichunk {
+       char            __user **ac_ubuffer;/* pointer into user's buffer */
+       int             ac_ubleft;      /* bytes left in user's buffer */
+       int             ac_ubelem;      /* spaces used in user's buffer */
+};
+
 /*
  * Process inodes in chunk with a pointer to a formatter function
  * that will iget the inode and fill in the appropriate structure.
  */
-int
+static int
 xfs_bulkstat_ag_ichunk(
        struct xfs_mount                *mp,
        xfs_agnumber_t                  agno,
        struct xfs_inobt_rec_incore     *irbp,
        bulkstat_one_pf                 formatter,
        size_t                          statstruct_size,
-       struct xfs_bulkstat_agichunk    *acp)
+       struct xfs_bulkstat_agichunk    *acp,
+       xfs_ino_t                       *lastino)
 {
-       xfs_ino_t                       lastino = acp->ac_lastino;
        char                            __user **ubufp = acp->ac_ubuffer;
-       int                             ubleft = acp->ac_ubleft;
-       int                             ubelem = acp->ac_ubelem;
-       int                             chunkidx, clustidx;
+       int                             chunkidx;
        int                             error = 0;
        xfs_agino_t                     agino;
 
-       for (agino = irbp->ir_startino, chunkidx = clustidx = 0;
-            XFS_BULKSTAT_UBLEFT(ubleft) &&
-            irbp->ir_freecount < XFS_INODES_PER_CHUNK;
-            chunkidx++, clustidx++, agino++) {
-               int             fmterror;       /* bulkstat formatter result */
+       agino = irbp->ir_startino;
+       for (chunkidx = 0; chunkidx < XFS_INODES_PER_CHUNK;
+            chunkidx++, agino++) {
+               int             fmterror;
                int             ubused;
                xfs_ino_t       ino = XFS_AGINO_TO_INO(mp, agno, agino);
 
-               ASSERT(chunkidx < XFS_INODES_PER_CHUNK);
-
                /* Skip if this inode is free */
                if (XFS_INOBT_MASK(chunkidx) & irbp->ir_free) {
-                       lastino = ino;
+                       *lastino = ino;
                        continue;
                }
 
-               /*
-                * Count used inodes as free so we can tell when the
-                * chunk is used up.
-                */
-               irbp->ir_freecount++;
-
                /* Get the inode and fill in a single buffer */
                ubused = statstruct_size;
-               error = formatter(mp, ino, *ubufp, ubleft, &ubused, &fmterror);
-               if (fmterror == BULKSTAT_RV_NOTHING) {
-                       if (error && error != -ENOENT && error != -EINVAL) {
-                               ubleft = 0;
-                               break;
-                       }
-                       lastino = ino;
-                       continue;
-               }
-               if (fmterror == BULKSTAT_RV_GIVEUP) {
-                       ubleft = 0;
+               error = formatter(mp, ino, *ubufp, acp->ac_ubleft,
+                                 &ubused, &fmterror);
+               if (fmterror == BULKSTAT_RV_GIVEUP ||
+                   (error && error != -ENOENT && error != -EINVAL)) {
+                       acp->ac_ubleft = 0;
                        ASSERT(error);
                        break;
                }
-               if (*ubufp)
-                       *ubufp += ubused;
-               ubleft -= ubused;
-               ubelem++;
-               lastino = ino;
-       }
 
-       acp->ac_lastino = lastino;
-       acp->ac_ubleft = ubleft;
-       acp->ac_ubelem = ubelem;
+               /* be careful not to leak error if at end of chunk */
+               if (fmterror == BULKSTAT_RV_NOTHING || error) {
+                       *lastino = ino;
+                       error = 0;
+                       continue;
+               }
+
+               *ubufp += ubused;
+               acp->ac_ubleft -= ubused;
+               acp->ac_ubelem++;
+               *lastino = ino;
+
+               if (acp->ac_ubleft < statstruct_size)
+                       break;
+       }
 
        return error;
 }
@@ -355,8 +350,6 @@ xfs_bulkstat(
        xfs_btree_cur_t         *cur;   /* btree cursor for ialloc btree */
        int                     end_of_ag; /* set if we've seen the ag end */
        int                     error;  /* error code */
-       int                     fmterror;/* bulkstat formatter result */
-       int                     i;      /* loop index */
        int                     icount; /* count of inodes good in irbuf */
        size_t                  irbsize; /* size of irec buffer in bytes */
        xfs_ino_t               ino;    /* inode number (filesystem) */
@@ -366,11 +359,9 @@ xfs_bulkstat(
        xfs_ino_t               lastino; /* last inode number returned */
        int                     nirbuf; /* size of irbuf */
        int                     rval;   /* return value error code */
-       int                     tmp;    /* result value from btree calls */
        int                     ubcount; /* size of user's buffer */
-       int                     ubleft; /* bytes left in user's buffer */
-       char                    __user *ubufp;  /* pointer into user's buffer */
-       int                     ubelem; /* spaces used in user's buffer */
+       int                     stat;
+       struct xfs_bulkstat_agichunk ac;
 
        /*
         * Get the last inode value, see if there's nothing to do.
@@ -387,11 +378,13 @@ xfs_bulkstat(
        }
 
        ubcount = *ubcountp; /* statstruct's */
-       ubleft = ubcount * statstruct_size; /* bytes */
-       *ubcountp = ubelem = 0;
+       ac.ac_ubuffer = &ubuffer;
+       ac.ac_ubleft = ubcount * statstruct_size; /* bytes */;
+       ac.ac_ubelem = 0;
+
+       *ubcountp = 0;
        *done = 0;
-       fmterror = 0;
-       ubufp = ubuffer;
+
        irbuf = kmem_zalloc_greedy(&irbsize, PAGE_SIZE, PAGE_SIZE * 4);
        if (!irbuf)
                return -ENOMEM;
@@ -403,7 +396,7 @@ xfs_bulkstat(
         * inode returned; 0 means start of the allocation group.
         */
        rval = 0;
-       while (XFS_BULKSTAT_UBLEFT(ubleft) && agno < mp->m_sb.sb_agcount) {
+       while (XFS_BULKSTAT_UBLEFT(ac.ac_ubleft) && agno < mp->m_sb.sb_agcount) {
                cond_resched();
                error = xfs_ialloc_read_agi(mp, NULL, agno, &agbp);
                if (error)
@@ -427,7 +420,7 @@ xfs_bulkstat(
 
                        error = xfs_bulkstat_grab_ichunk(cur, agino, &icount, &r);
                        if (error)
-                               break;
+                               goto del_cursor;
                        if (icount) {
                                irbp->ir_startino = r.ir_startino;
                                irbp->ir_freecount = r.ir_freecount;
@@ -436,13 +429,15 @@ xfs_bulkstat(
                                agino = r.ir_startino + XFS_INODES_PER_CHUNK;
                        }
                        /* Increment to the next record */
-                       error = xfs_btree_increment(cur, 0, &tmp);
+                       error = xfs_btree_increment(cur, 0, &stat);
                } else {
                        /* Start of ag.  Lookup the first inode chunk */
-                       error = xfs_inobt_lookup(cur, 0, XFS_LOOKUP_GE, &tmp);
+                       error = xfs_inobt_lookup(cur, 0, XFS_LOOKUP_GE, &stat);
+               }
+               if (error || stat == 0) {
+                       end_of_ag = 1;
+                       goto del_cursor;
                }
-               if (error)
-                       break;
 
                /*
                 * Loop through inode btree records in this ag,
@@ -451,10 +446,10 @@ xfs_bulkstat(
                while (irbp < irbufend && icount < ubcount) {
                        struct xfs_inobt_rec_incore     r;
 
-                       error = xfs_inobt_get_rec(cur, &r, &i);
-                       if (error || i == 0) {
+                       error = xfs_inobt_get_rec(cur, &r, &stat);
+                       if (error || stat == 0) {
                                end_of_ag = 1;
-                               break;
+                               goto del_cursor;
                        }
 
                        /*
@@ -473,43 +468,44 @@ xfs_bulkstat(
                         * Set agino to after this chunk and bump the cursor.
                         */
                        agino = r.ir_startino + XFS_INODES_PER_CHUNK;
-                       error = xfs_btree_increment(cur, 0, &tmp);
+                       error = xfs_btree_increment(cur, 0, &stat);
+                       if (error || stat == 0) {
+                               end_of_ag = 1;
+                               goto del_cursor;
+                       }
                        cond_resched();
                }
+
                /*
-                * Drop the btree buffers and the agi buffer.
-                * We can't hold any of the locks these represent
-                * when calling iget.
+                * Drop the btree buffers and the agi buffer as we can't hold any
+                * of the locks these represent when calling iget. If there is a
+                * pending error, then we are done.
                 */
+del_cursor:
                xfs_btree_del_cursor(cur, XFS_BTREE_NOERROR);
                xfs_buf_relse(agbp);
+               if (error)
+                       break;
                /*
                 * Now format all the good inodes into the user's buffer.
                 */
                irbufend = irbp;
                for (irbp = irbuf;
-                    irbp < irbufend && XFS_BULKSTAT_UBLEFT(ubleft); irbp++) {
-                       struct xfs_bulkstat_agichunk ac;
-
-                       ac.ac_lastino = lastino;
-                       ac.ac_ubuffer = &ubuffer;
-                       ac.ac_ubleft = ubleft;
-                       ac.ac_ubelem = ubelem;
+                    irbp < irbufend && XFS_BULKSTAT_UBLEFT(ac.ac_ubleft);
+                    irbp++) {
                        error = xfs_bulkstat_ag_ichunk(mp, agno, irbp,
-                                       formatter, statstruct_size, &ac);
+                                       formatter, statstruct_size, &ac,
+                                       &lastino);
                        if (error)
                                rval = error;
 
-                       lastino = ac.ac_lastino;
-                       ubleft = ac.ac_ubleft;
-                       ubelem = ac.ac_ubelem;
-
                        cond_resched();
                }
+
                /*
                 * Set up for the next loop iteration.
                 */
-               if (XFS_BULKSTAT_UBLEFT(ubleft)) {
+               if (XFS_BULKSTAT_UBLEFT(ac.ac_ubleft)) {
                        if (end_of_ag) {
                                agno++;
                                agino = 0;
@@ -522,11 +518,11 @@ xfs_bulkstat(
         * Done, we're either out of filesystem or space to put the data.
         */
        kmem_free(irbuf);
-       *ubcountp = ubelem;
+       *ubcountp = ac.ac_ubelem;
        /*
         * Found some inodes, return them now and return the error next time.
         */
-       if (ubelem)
+       if (ac.ac_ubelem)
                rval = 0;
        if (agno >= mp->m_sb.sb_agcount) {
                /*