@@ -1436,6 +1436,7 @@ xfs_ioctl_setattr(
struct xfs_trans *tp;
struct xfs_dquot *pdqp = NULL;
struct xfs_dquot *olddquot = NULL;
+ unsigned int quota_retry = 0;
int code;
trace_xfs_ioctl_setattr(ip);
@@ -1462,6 +1463,7 @@ xfs_ioctl_setattr(
xfs_ioctl_setattr_prepare_dax(ip, fa);
+retry:
tp = xfs_ioctl_setattr_get_trans(ip);
if (IS_ERR(tp)) {
code = PTR_ERR(tp);
@@ -1471,9 +1473,15 @@ xfs_ioctl_setattr(
if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp) &&
ip->i_d.di_projid != fa->fsx_projid) {
code = xfs_trans_reserve_quota_chown(tp, ip, NULL, NULL, pdqp,
- capable(CAP_FOWNER));
+ capable(CAP_FOWNER), "a_retry);
if (code) /* out of quota */
goto error_trans_cancel;
+ if (quota_retry) {
+ xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ xfs_blockgc_free_dquots(NULL, NULL, pdqp, 0);
+ goto retry;
+ }
}
xfs_fill_fsxattr(ip, false, &old_fa);
@@ -660,6 +660,7 @@ xfs_setattr_nonsize(
kgid_t gid = GLOBAL_ROOT_GID, igid = GLOBAL_ROOT_GID;
struct xfs_dquot *udqp = NULL, *gdqp = NULL;
struct xfs_dquot *olddquot1 = NULL, *olddquot2 = NULL;
+ unsigned int quota_retry = 0;
ASSERT((mask & ATTR_SIZE) == 0);
@@ -700,6 +701,7 @@ xfs_setattr_nonsize(
return error;
}
+retry:
error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp);
if (error)
goto out_dqrele;
@@ -731,9 +733,16 @@ xfs_setattr_nonsize(
(XFS_IS_GQUOTA_ON(mp) && !gid_eq(igid, gid)))) {
ASSERT(tp);
error = xfs_trans_reserve_quota_chown(tp, ip, udqp,
- gdqp, NULL, capable(CAP_FOWNER));
+ gdqp, NULL, capable(CAP_FOWNER),
+ "a_retry);
if (error) /* out of quota */
goto out_cancel;
+ if (quota_retry) {
+ xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ xfs_blockgc_free_dquots(udqp, gdqp, NULL, 0);
+ goto retry;
+ }
}
/*
@@ -101,7 +101,7 @@ extern struct xfs_dquot *xfs_qm_vop_chown(struct xfs_trans *,
struct xfs_inode *, struct xfs_dquot **, struct xfs_dquot *);
int xfs_trans_reserve_quota_chown(struct xfs_trans *tp, struct xfs_inode *ip,
struct xfs_dquot *udqp, struct xfs_dquot *gdqp,
- struct xfs_dquot *pdqp, bool force);
+ struct xfs_dquot *pdqp, bool force, unsigned int *retry);
extern int xfs_qm_dqattach(struct xfs_inode *);
extern int xfs_qm_dqattach_locked(struct xfs_inode *ip, bool doalloc);
extern void xfs_qm_dqdetach(struct xfs_inode *);
@@ -167,7 +167,7 @@ xfs_trans_reserve_quota_icreate(struct xfs_trans *tp, struct xfs_dquot *udqp,
static inline int
xfs_trans_reserve_quota_chown(struct xfs_trans *tp, struct xfs_inode *ip,
struct xfs_dquot *udqp, struct xfs_dquot *gdqp,
- struct xfs_dquot *pdqp, bool force)
+ struct xfs_dquot *pdqp, bool force, unsigned int *retry)
{
return 0;
}
@@ -907,7 +907,15 @@ xfs_trans_reserve_quota_icreate(
}
/*
- * Quota reservations for setattr(AT_UID|AT_GID|AT_PROJID).
+ * Chagnge quota reservations for setattr(AT_UID|AT_GID|AT_PROJID). This
+ * doesn't change the actual usage, just the reservation. The caller must hold
+ * ILOCK_EXCL on the inode. If @retry is not a NULL pointer, the caller must
+ * ensure that *retry is set to zero before the first time this function is
+ * called.
+ *
+ * If the quota reservation fails because we hit a quota limit (and retry is
+ * not a NULL pointer, and *retry is zero), this function will set *retry to
+ * nonzero and return zero.
*/
int
xfs_trans_reserve_quota_chown(
@@ -916,7 +924,8 @@ xfs_trans_reserve_quota_chown(
struct xfs_dquot *udqp,
struct xfs_dquot *gdqp,
struct xfs_dquot *pdqp,
- bool force)
+ bool force,
+ unsigned int *retry)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_dquot *udq_unres = NULL; /* old dquots */
@@ -976,7 +985,7 @@ xfs_trans_reserve_quota_chown(
gdq_delblks, pdq_delblks, ip->i_d.di_nblocks, 1,
qflags);
if (error)
- return error;
+ goto err;
/*
* Do the delayed blks reservations/unreservations now. Since, these
@@ -994,13 +1003,24 @@ xfs_trans_reserve_quota_chown(
udq_delblks, gdq_delblks, pdq_delblks,
(xfs_qcnt_t)delblks, 0, qflags);
if (error)
- return error;
+ goto err;
xfs_trans_reserve_quota_bydquots(NULL, ip->i_mount, udq_unres,
gdq_unres, pdq_unres, -((xfs_qcnt_t)delblks),
0, qflags);
}
return 0;
+err:
+ /*
+ * Handle all quota reservation failures in the same place because we
+ * don't want reservation_success() to clear REGBLKS from the retry
+ * flags after we _reserve_quota for the ondisk blocks but before we
+ * _reserve_quota for the delalloc blocks. If we were called with a
+ * nonzero *retry, that means we failed to get the quota reservation
+ * once before and do not want to schedule a retry on a second error.
+ */
+ reservation_success(XFS_QMOPT_RES_REGBLKS, retry, &error);
+ return error;
}
/*