@@ -1436,6 +1436,7 @@ xfs_ioctl_setattr(
struct xfs_trans *tp;
struct xfs_dquot *pdqp = NULL;
struct xfs_dquot *olddquot = NULL;
+ bool quota_retry = false;
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);
@@ -1470,10 +1472,16 @@ xfs_ioctl_setattr(
if (XFS_IS_QUOTA_RUNNING(mp) && XFS_IS_PQUOTA_ON(mp) &&
ip->i_d.di_projid != fa->fsx_projid) {
- code = xfs_qm_vop_chown_reserve(tp, ip, NULL, NULL, pdqp,
- capable(CAP_FOWNER) ? XFS_QMOPT_FORCE_RES : 0);
- if (code) /* out of quota */
- goto error_trans_cancel;
+ unsigned int flags = 0;
+
+ if (capable(CAP_FOWNER))
+ flags |= XFS_QMOPT_FORCE_RES;
+ code = xfs_qm_vop_chown_reserve(&tp, ip, NULL, NULL, pdqp,
+ flags, "a_retry);
+ if (code)
+ goto error_free_dquots;
+ if (quota_retry)
+ 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;
+ bool quota_retry = false;
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;
@@ -729,12 +731,17 @@ xfs_setattr_nonsize(
if (XFS_IS_QUOTA_RUNNING(mp) &&
((XFS_IS_UQUOTA_ON(mp) && !uid_eq(iuid, uid)) ||
(XFS_IS_GQUOTA_ON(mp) && !gid_eq(igid, gid)))) {
+ unsigned int flags = 0;
+
+ if (capable(CAP_FOWNER))
+ flags |= XFS_QMOPT_FORCE_RES;
ASSERT(tp);
- error = xfs_qm_vop_chown_reserve(tp, ip, udqp, gdqp,
- NULL, capable(CAP_FOWNER) ?
- XFS_QMOPT_FORCE_RES : 0);
- if (error) /* out of quota */
- goto out_cancel;
+ error = xfs_qm_vop_chown_reserve(&tp, ip, udqp, gdqp,
+ NULL, flags, "a_retry);
+ if (error)
+ goto out_dqrele;
+ if (quota_retry)
+ goto retry;
}
/*
@@ -814,9 +821,6 @@ xfs_setattr_nonsize(
return 0;
-out_cancel:
- xfs_trans_cancel(tp);
- xfs_iunlock(ip, XFS_ILOCK_EXCL);
out_dqrele:
xfs_qm_dqrele(udqp);
xfs_qm_dqrele(gdqp);
@@ -1795,27 +1795,29 @@ xfs_qm_vop_chown(
}
/*
- * Quota reservations for setattr(AT_UID|AT_GID|AT_PROJID).
+ * Quota reservations for setattr(AT_UID|AT_GID|AT_PROJID). This function has
+ * the same return behavior as xfs_trans_reserve_quota_nblks.
*/
int
xfs_qm_vop_chown_reserve(
- struct xfs_trans *tp,
+ struct xfs_trans **tpp,
struct xfs_inode *ip,
struct xfs_dquot *udqp,
struct xfs_dquot *gdqp,
struct xfs_dquot *pdqp,
- uint flags)
+ unsigned int flags,
+ bool *retry)
{
struct xfs_mount *mp = ip->i_mount;
uint64_t delblks;
unsigned int blkflags;
- struct xfs_dquot *udq_unres = NULL;
+ struct xfs_dquot *udq_unres = NULL; /* old dquots */
struct xfs_dquot *gdq_unres = NULL;
struct xfs_dquot *pdq_unres = NULL;
- struct xfs_dquot *udq_delblks = NULL;
+ struct xfs_dquot *udq_delblks = NULL; /* new dquots */
struct xfs_dquot *gdq_delblks = NULL;
struct xfs_dquot *pdq_delblks = NULL;
- int error;
+ int error, err2;
ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_ILOCK_SHARED));
@@ -1856,11 +1858,11 @@ xfs_qm_vop_chown_reserve(
}
}
- error = xfs_trans_reserve_quota_bydquots(tp, ip->i_mount,
+ error = xfs_trans_reserve_quota_bydquots(*tpp, ip->i_mount,
udq_delblks, gdq_delblks, pdq_delblks,
ip->i_d.di_nblocks, 1, flags | blkflags);
if (error)
- return error;
+ goto err;
/*
* Do the delayed blks reservations/unreservations now. Since, these
@@ -1878,13 +1880,29 @@ xfs_qm_vop_chown_reserve(
udq_delblks, gdq_delblks, pdq_delblks,
(xfs_qcnt_t)delblks, 0, flags | blkflags);
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, blkflags);
}
return 0;
+err:
+ xfs_trans_cancel(*tpp);
+ *tpp = NULL;
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+ /* We only allow one retry for EDQUOT/ENOSPC. */
+ if (*retry || (error != -EDQUOT && error != -ENOSPC)) {
+ *retry = false;
+ return error;
+ }
+
+ /* Try to free some quota in the new dquots. */
+ err2 = xfs_blockgc_free_dquots(udq_delblks, gdq_delblks, pdq_delblks,
+ 0, retry);
+ if (err2)
+ return err2;
+ return *retry ? 0 : error;
}
int
@@ -100,9 +100,9 @@ extern void xfs_qm_vop_create_dqattach(struct xfs_trans *, struct xfs_inode *,
extern int xfs_qm_vop_rename_dqattach(struct xfs_inode **);
extern struct xfs_dquot *xfs_qm_vop_chown(struct xfs_trans *,
struct xfs_inode *, struct xfs_dquot **, struct xfs_dquot *);
-extern int xfs_qm_vop_chown_reserve(struct xfs_trans *, struct xfs_inode *,
- struct xfs_dquot *, struct xfs_dquot *,
- struct xfs_dquot *, uint);
+int xfs_qm_vop_chown_reserve(struct xfs_trans **tpp, struct xfs_inode *ip,
+ struct xfs_dquot *udqp, struct xfs_dquot *gdqp,
+ struct xfs_dquot *pdqp, unsigned int flags, bool *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 *);
@@ -157,7 +157,7 @@ xfs_trans_reserve_quota_icreate(struct xfs_trans **tpp, struct xfs_inode *dp,
#define xfs_qm_vop_create_dqattach(tp, ip, u, g, p)
#define xfs_qm_vop_rename_dqattach(it) (0)
#define xfs_qm_vop_chown(tp, ip, old, new) (NULL)
-#define xfs_qm_vop_chown_reserve(tp, ip, u, g, p, fl) (0)
+#define xfs_qm_vop_chown_reserve(tpp, ip, u, g, p, fl, retry) (0)
#define xfs_qm_dqattach(ip) (0)
#define xfs_qm_dqattach_locked(ip, fl) (0)
#define xfs_qm_dqdetach(ip)