xfs: flush speculative space allocations when we run out of space
[linux-2.6-microblaze.git] / fs / xfs / xfs_trans.c
index 6c68635..44f72c0 100644 (file)
@@ -23,6 +23,7 @@
 #include "xfs_inode.h"
 #include "xfs_dquot_item.h"
 #include "xfs_dquot.h"
+#include "xfs_icache.h"
 
 kmem_zone_t    *xfs_trans_zone;
 
@@ -288,6 +289,17 @@ xfs_trans_alloc(
        tp->t_firstblock = NULLFSBLOCK;
 
        error = xfs_trans_reserve(tp, resp, blocks, rtextents);
+       if (error == -ENOSPC) {
+               /*
+                * We weren't able to reserve enough space for the transaction.
+                * Flush the other speculative space allocations to free space.
+                * Do not perform a synchronous scan because callers can hold
+                * other locks.
+                */
+               error = xfs_blockgc_free_space(mp, NULL);
+               if (!error)
+                       error = xfs_trans_reserve(tp, resp, blocks, rtextents);
+       }
        if (error) {
                xfs_trans_cancel(tp);
                return error;
@@ -1046,8 +1058,10 @@ xfs_trans_alloc_inode(
 {
        struct xfs_trans        *tp;
        struct xfs_mount        *mp = ip->i_mount;
+       bool                    retried = false;
        int                     error;
 
+retry:
        error = xfs_trans_alloc(mp, resv, dblocks,
                        rblocks / mp->m_sb.sb_rextsize,
                        force ? XFS_TRANS_RESERVE : 0, &tp);
@@ -1065,6 +1079,13 @@ xfs_trans_alloc_inode(
        }
 
        error = xfs_trans_reserve_quota_nblks(tp, ip, dblocks, rblocks, force);
+       if ((error == -EDQUOT || error == -ENOSPC) && !retried) {
+               xfs_trans_cancel(tp);
+               xfs_iunlock(ip, XFS_ILOCK_EXCL);
+               xfs_blockgc_free_quota(ip, 0);
+               retried = true;
+               goto retry;
+       }
        if (error)
                goto out_cancel;
 
@@ -1092,13 +1113,21 @@ xfs_trans_alloc_icreate(
        struct xfs_trans        **tpp)
 {
        struct xfs_trans        *tp;
+       bool                    retried = false;
        int                     error;
 
+retry:
        error = xfs_trans_alloc(mp, resv, dblocks, 0, 0, &tp);
        if (error)
                return error;
 
        error = xfs_trans_reserve_quota_icreate(tp, udqp, gdqp, pdqp, dblocks);
+       if ((error == -EDQUOT || error == -ENOSPC) && !retried) {
+               xfs_trans_cancel(tp);
+               xfs_blockgc_free_dquots(mp, udqp, gdqp, pdqp, 0);
+               retried = true;
+               goto retry;
+       }
        if (error) {
                xfs_trans_cancel(tp);
                return error;
@@ -1107,3 +1136,86 @@ xfs_trans_alloc_icreate(
        *tpp = tp;
        return 0;
 }
+
+/*
+ * Allocate an transaction, lock and join the inode to it, and reserve quota
+ * in preparation for inode attribute changes that include uid, gid, or prid
+ * changes.
+ *
+ * The caller must ensure that the on-disk dquots attached to this inode have
+ * already been allocated and initialized.  The ILOCK will be dropped when the
+ * transaction is committed or cancelled.
+ */
+int
+xfs_trans_alloc_ichange(
+       struct xfs_inode        *ip,
+       struct xfs_dquot        *new_udqp,
+       struct xfs_dquot        *new_gdqp,
+       struct xfs_dquot        *new_pdqp,
+       bool                    force,
+       struct xfs_trans        **tpp)
+{
+       struct xfs_trans        *tp;
+       struct xfs_mount        *mp = ip->i_mount;
+       struct xfs_dquot        *udqp;
+       struct xfs_dquot        *gdqp;
+       struct xfs_dquot        *pdqp;
+       bool                    retried = false;
+       int                     error;
+
+retry:
+       error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
+       if (error)
+               return error;
+
+       xfs_ilock(ip, XFS_ILOCK_EXCL);
+       xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+
+       error = xfs_qm_dqattach_locked(ip, false);
+       if (error) {
+               /* Caller should have allocated the dquots! */
+               ASSERT(error != -ENOENT);
+               goto out_cancel;
+       }
+
+       /*
+        * For each quota type, skip quota reservations if the inode's dquots
+        * now match the ones that came from the caller, or the caller didn't
+        * pass one in.  The inode's dquots can change if we drop the ILOCK to
+        * perform a blockgc scan, so we must preserve the caller's arguments.
+        */
+       udqp = (new_udqp != ip->i_udquot) ? new_udqp : NULL;
+       gdqp = (new_gdqp != ip->i_gdquot) ? new_gdqp : NULL;
+       pdqp = (new_pdqp != ip->i_pdquot) ? new_pdqp : NULL;
+       if (udqp || gdqp || pdqp) {
+               unsigned int    qflags = XFS_QMOPT_RES_REGBLKS;
+
+               if (force)
+                       qflags |= XFS_QMOPT_FORCE_RES;
+
+               /*
+                * Reserve enough quota to handle blocks on disk and reserved
+                * for a delayed allocation.  We'll actually transfer the
+                * delalloc reservation between dquots at chown time, even
+                * though that part is only semi-transactional.
+                */
+               error = xfs_trans_reserve_quota_bydquots(tp, mp, udqp, gdqp,
+                               pdqp, ip->i_d.di_nblocks + ip->i_delayed_blks,
+                               1, qflags);
+               if ((error == -EDQUOT || error == -ENOSPC) && !retried) {
+                       xfs_trans_cancel(tp);
+                       xfs_blockgc_free_dquots(mp, udqp, gdqp, pdqp, 0);
+                       retried = true;
+                       goto retry;
+               }
+               if (error)
+                       goto out_cancel;
+       }
+
+       *tpp = tp;
+       return 0;
+
+out_cancel:
+       xfs_trans_cancel(tp);
+       return error;
+}