Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

xfs: reserve quota for target dir expansion when renaming files

XFS does not reserve quota for directory expansion when renaming
children into a directory. This means that we don't reject the
expansion with EDQUOT when we're at or near a hard limit, which means
that unprivileged userspace can use rename() to exceed quota.

Rename operations don't always expand the target directory, and we allow
a rename to proceed with no space reservation if we don't need to add a
block to the target directory to handle the addition. Moreover, the
unlink operation on the source directory generally does not expand the
directory (you'd have to free a block and then cause a btree split) and
it's probably of little consequence to leave the corner case that
renaming a file out of a directory can increase its size.

As with link and unlink, there is a further bug in that we do not
trigger the blockgc workers to try to clear space when we're out of
quota.

Because rename is its own special tricky animal, we'll patch xfs_rename
directly to reserve quota to the rename transaction. We'll leave
cleaning up the rest of xfs_rename for the metadata directory tree
patchset.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>

+32 -1
+32 -1
fs/xfs/xfs_inode.c
··· 3097 3097 bool new_parent = (src_dp != target_dp); 3098 3098 bool src_is_directory = S_ISDIR(VFS_I(src_ip)->i_mode); 3099 3099 int spaceres; 3100 - int error; 3100 + bool retried = false; 3101 + int error, nospace_error = 0; 3101 3102 3102 3103 trace_xfs_rename(src_dp, target_dp, src_name, target_name); 3103 3104 ··· 3122 3121 xfs_sort_for_rename(src_dp, target_dp, src_ip, target_ip, wip, 3123 3122 inodes, &num_inodes); 3124 3123 3124 + retry: 3125 + nospace_error = 0; 3125 3126 spaceres = XFS_RENAME_SPACE_RES(mp, target_name->len); 3126 3127 error = xfs_trans_alloc(mp, &M_RES(mp)->tr_rename, spaceres, 0, 0, &tp); 3127 3128 if (error == -ENOSPC) { 3129 + nospace_error = error; 3128 3130 spaceres = 0; 3129 3131 error = xfs_trans_alloc(mp, &M_RES(mp)->tr_rename, 0, 0, 0, 3130 3132 &tp); ··· 3180 3176 return xfs_cross_rename(tp, src_dp, src_name, src_ip, 3181 3177 target_dp, target_name, target_ip, 3182 3178 spaceres); 3179 + 3180 + /* 3181 + * Try to reserve quota to handle an expansion of the target directory. 3182 + * We'll allow the rename to continue in reservationless mode if we hit 3183 + * a space usage constraint. If we trigger reservationless mode, save 3184 + * the errno if there isn't any free space in the target directory. 3185 + */ 3186 + if (spaceres != 0) { 3187 + error = xfs_trans_reserve_quota_nblks(tp, target_dp, spaceres, 3188 + 0, false); 3189 + if (error == -EDQUOT || error == -ENOSPC) { 3190 + if (!retried) { 3191 + xfs_trans_cancel(tp); 3192 + xfs_blockgc_free_quota(target_dp, 0); 3193 + retried = true; 3194 + goto retry; 3195 + } 3196 + 3197 + nospace_error = error; 3198 + spaceres = 0; 3199 + error = 0; 3200 + } 3201 + if (error) 3202 + goto out_trans_cancel; 3203 + } 3183 3204 3184 3205 /* 3185 3206 * Check for expected errors before we dirty the transaction ··· 3452 3423 out_release_wip: 3453 3424 if (wip) 3454 3425 xfs_irele(wip); 3426 + if (error == -ENOSPC && nospace_error) 3427 + error = nospace_error; 3455 3428 return error; 3456 3429 } 3457 3430