xfs: reserve quota for dir expansion when linking/unlinking files
[linux-block.git] / fs / xfs / xfs_trans.c
index 59e2f9031b9f8c511d62f3874678f011e992d917..3d11f9bb0dbbcf2500f0768a534a7efe38381aa0 100644 (file)
@@ -1210,3 +1210,89 @@ out_cancel:
        xfs_trans_cancel(tp);
        return error;
 }
+
+/*
+ * Allocate an transaction, lock and join the directory and child inodes to it,
+ * and reserve quota for a directory update.  If there isn't sufficient space,
+ * @dblocks will be set to zero for a reservationless directory update and
+ * @nospace_error will be set to a negative errno describing the space
+ * constraint we hit.
+ *
+ * The caller must ensure that the on-disk dquots attached to this inode have
+ * already been allocated and initialized.  The ILOCKs will be dropped when the
+ * transaction is committed or cancelled.
+ */
+int
+xfs_trans_alloc_dir(
+       struct xfs_inode        *dp,
+       struct xfs_trans_res    *resv,
+       struct xfs_inode        *ip,
+       unsigned int            *dblocks,
+       struct xfs_trans        **tpp,
+       int                     *nospace_error)
+{
+       struct xfs_trans        *tp;
+       struct xfs_mount        *mp = ip->i_mount;
+       unsigned int            resblks;
+       bool                    retried = false;
+       int                     error;
+
+retry:
+       *nospace_error = 0;
+       resblks = *dblocks;
+       error = xfs_trans_alloc(mp, resv, resblks, 0, 0, &tp);
+       if (error == -ENOSPC) {
+               *nospace_error = error;
+               resblks = 0;
+               error = xfs_trans_alloc(mp, resv, resblks, 0, 0, &tp);
+       }
+       if (error)
+               return error;
+
+       xfs_lock_two_inodes(dp, XFS_ILOCK_EXCL, ip, XFS_ILOCK_EXCL);
+
+       xfs_trans_ijoin(tp, dp, XFS_ILOCK_EXCL);
+       xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
+
+       error = xfs_qm_dqattach_locked(dp, false);
+       if (error) {
+               /* Caller should have allocated the dquots! */
+               ASSERT(error != -ENOENT);
+               goto out_cancel;
+       }
+
+       error = xfs_qm_dqattach_locked(ip, false);
+       if (error) {
+               /* Caller should have allocated the dquots! */
+               ASSERT(error != -ENOENT);
+               goto out_cancel;
+       }
+
+       if (resblks == 0)
+               goto done;
+
+       error = xfs_trans_reserve_quota_nblks(tp, dp, resblks, 0, false);
+       if (error == -EDQUOT || error == -ENOSPC) {
+               if (!retried) {
+                       xfs_trans_cancel(tp);
+                       xfs_blockgc_free_quota(dp, 0);
+                       retried = true;
+                       goto retry;
+               }
+
+               *nospace_error = error;
+               resblks = 0;
+               error = 0;
+       }
+       if (error)
+               goto out_cancel;
+
+done:
+       *tpp = tp;
+       *dblocks = resblks;
+       return 0;
+
+out_cancel:
+       xfs_trans_cancel(tp);
+       return error;
+}