[XFS] Account for allocated blocks when expanding directories
authorDavid Chinner <david@fromorbit.com>
Thu, 30 Oct 2008 06:38:12 +0000 (17:38 +1100)
committerLachlan McIlroy <lachlan@redback.melbourne.sgi.com>
Mon, 10 Nov 2008 06:51:14 +0000 (17:51 +1100)
When we create a directory, we reserve a number of blocks for the maximum
possible expansion of of the directory due to various btree splits,
freespace allocation, etc. Unfortunately, each allocation is not reflected
in the total number of blocks still available to the transaction, so the
maximal reservation is used over and over again.

This leads to problems where an allocation group has only enough blocks
for *some* of the allocations required for the directory modification.
After the first N allocations, the remaining blocks in the allocation
group drops below the total reservation, and subsequent allocations fail
because the allocator will not allow the allocation to proceed if the AG
does not have the enough blocks available for the entire allocation total.

This results in an ENOSPC occurring after an allocation has already
occurred. This results in aborting the directory operation (leaving the
directory in an inconsistent state) and cancelling a dirty transaction,
which results in a filesystem shutdown.

Avoid the problem by reflecting the number of blocks allocated in any
directory expansion in the total number of blocks available to the
modification in progress. This prevents a directory modification from
being aborted part way through with an ENOSPC.

SGI-PV: 988144

SGI-Modid: xfs-linux-melb:xfs-kern:32340a

Signed-off-by: David Chinner <david@fromorbit.com>
Signed-off-by: Lachlan McIlroy <lachlan@sgi.com>
fs/xfs/xfs_da_btree.c
fs/xfs/xfs_dir2.c

index 9e561a9cefcaa193c5a4214ad7449246dcd96a44..a11a8390bf6caa7d12270e182efabee7f5fcffac 100644 (file)
@@ -1566,11 +1566,14 @@ xfs_da_grow_inode(xfs_da_args_t *args, xfs_dablk_t *new_blkno)
        int nmap, error, w, count, c, got, i, mapi;
        xfs_trans_t *tp;
        xfs_mount_t *mp;
+       xfs_drfsbno_t   nblks;
 
        dp = args->dp;
        mp = dp->i_mount;
        w = args->whichfork;
        tp = args->trans;
+       nblks = dp->i_d.di_nblocks;
+
        /*
         * For new directories adjust the file offset and block count.
         */
@@ -1647,6 +1650,8 @@ xfs_da_grow_inode(xfs_da_args_t *args, xfs_dablk_t *new_blkno)
        }
        if (mapp != &map)
                kmem_free(mapp);
+       /* account for newly allocated blocks in reserved blocks total */
+       args->total -= dp->i_d.di_nblocks - nblks;
        *new_blkno = (xfs_dablk_t)bno;
        return 0;
 }
index 80e0dc51361c07e541726ff75871f399fd58d181..1afb12278b8d04b85eb42d0f67d0bd21936885be 100644 (file)
@@ -525,11 +525,13 @@ xfs_dir2_grow_inode(
        xfs_mount_t     *mp;
        int             nmap;           /* number of bmap entries */
        xfs_trans_t     *tp;
+       xfs_drfsbno_t   nblks;
 
        xfs_dir2_trace_args_s("grow_inode", args, space);
        dp = args->dp;
        tp = args->trans;
        mp = dp->i_mount;
+       nblks = dp->i_d.di_nblocks;
        /*
         * Set lowest possible block in the space requested.
         */
@@ -622,7 +624,11 @@ xfs_dir2_grow_inode(
         */
        if (mapp != &map)
                kmem_free(mapp);
+
+       /* account for newly allocated blocks in reserved blocks total */
+       args->total -= dp->i_d.di_nblocks - nblks;
        *dbp = xfs_dir2_da_to_db(mp, (xfs_dablk_t)bno);
+
        /*
         * Update file's size if this is the data space and it grew.
         */