gfs2: Change inode qa_data to allow multiple users

Before this patch, multiple users called gfs2_qa_alloc which allocated
a qadata structure to the inode, if quotas are turned on. Later, in
file close or evict, the structure was deleted with gfs2_qa_delete.
But there can be several competing processes who need access to the
structure. There were races between file close (release) and the others.
Thus, a release could delete the structure out from under a process
that relied upon its existence. For example, chown.

This patch changes the management of the qadata structures to be
a get/put scheme. Function gfs2_qa_alloc has been changed to gfs2_qa_get
and if the structure is allocated, the count essentially starts out at
1. Function gfs2_qa_delete has been renamed to gfs2_qa_put, and the
last guy to decrement the count to 0 frees the memory.

Signed-off-by: Bob Peterson <rpeterso@redhat.com>
This commit is contained in:
Bob Peterson 2020-02-27 12:47:53 -06:00
parent d580712a37
commit 2fba46a04c
10 changed files with 101 additions and 58 deletions

View File

@ -117,14 +117,14 @@ int gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
if (acl && acl->a_count > GFS2_ACL_MAX_ENTRIES(GFS2_SB(inode)))
return -E2BIG;
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
return ret;
if (!gfs2_glock_is_locked_by_me(ip->i_gl)) {
ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh);
if (ret)
return ret;
goto out;
need_unlock = true;
}
@ -144,5 +144,7 @@ int gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type)
unlock:
if (need_unlock)
gfs2_glock_dq_uninit(&gh);
out:
gfs2_qa_put(ip);
return ret;
}

View File

@ -2183,7 +2183,7 @@ int gfs2_setattr_size(struct inode *inode, u64 newsize)
inode_dio_wait(inode);
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
goto out;

View File

@ -458,7 +458,7 @@ static vm_fault_t gfs2_page_mkwrite(struct vm_fault *vmf)
sb_start_pagefault(inode->i_sb);
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
goto out;
@ -553,6 +553,7 @@ out_quota_unlock:
out_unlock:
gfs2_glock_dq(&gh);
out_uninit:
gfs2_qa_put(ip);
gfs2_holder_uninit(&gh);
if (ret == 0) {
set_page_dirty(page);
@ -635,7 +636,17 @@ int gfs2_open_common(struct inode *inode, struct file *file)
gfs2_assert_warn(GFS2_SB(inode), !file->private_data);
file->private_data = fp;
if (file->f_mode & FMODE_WRITE) {
ret = gfs2_qa_get(GFS2_I(inode));
if (ret)
goto fail;
}
return 0;
fail:
kfree(file->private_data);
file->private_data = NULL;
return ret;
}
/**
@ -690,10 +701,8 @@ static int gfs2_release(struct inode *inode, struct file *file)
kfree(file->private_data);
file->private_data = NULL;
if (!(file->f_mode & FMODE_WRITE))
return 0;
gfs2_rsqa_delete(ip, &inode->i_writecount);
if (file->f_mode & FMODE_WRITE)
gfs2_rsqa_delete(ip, &inode->i_writecount);
return 0;
}
@ -849,7 +858,7 @@ static ssize_t gfs2_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
struct gfs2_inode *ip = GFS2_I(inode);
ssize_t ret;
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
return ret;
@ -860,7 +869,7 @@ static ssize_t gfs2_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, 0, &gh);
if (ret)
return ret;
goto out;
gfs2_glock_dq_uninit(&gh);
}
@ -918,6 +927,8 @@ static ssize_t gfs2_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
out_unlock:
inode_unlock(inode);
out:
gfs2_qa_put(ip);
return ret;
}
@ -1149,7 +1160,7 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le
if (mode & FALLOC_FL_PUNCH_HOLE) {
ret = __gfs2_punch_hole(file, offset, len);
} else {
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
goto out_putw;
@ -1157,6 +1168,7 @@ static long gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t le
if (ret)
gfs2_rs_deltree(&ip->i_res);
gfs2_qa_put(ip);
}
out_putw:
@ -1175,14 +1187,17 @@ static ssize_t gfs2_file_splice_write(struct pipe_inode_info *pipe,
{
int error;
struct gfs2_inode *ip = GFS2_I(out->f_mapping->host);
ssize_t ret;
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
return (ssize_t)error;
gfs2_size_hint(out, *ppos, len);
return iter_file_splice_write(pipe, out, ppos, len, flags);
ret = iter_file_splice_write(pipe, out, ppos, len, flags);
gfs2_qa_put(ip);
return ret;
}
#ifdef CONFIG_GFS2_FS_LOCKING_DLM

View File

@ -295,6 +295,7 @@ struct gfs2_qadata { /* quota allocation data */
struct gfs2_quota_data *qa_qd[2 * GFS2_MAXQUOTAS];
struct gfs2_holder qa_qd_ghs[2 * GFS2_MAXQUOTAS];
unsigned int qa_qd_num;
int qa_ref;
};
/* Resource group multi-block reservation, in order of appearance:

View File

@ -588,13 +588,13 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry,
if (!name->len || name->len > GFS2_FNAMESIZE)
return -ENAMETOOLONG;
error = gfs2_qa_alloc(dip);
error = gfs2_qa_get(dip);
if (error)
return error;
error = gfs2_rindex_update(sdp);
if (error)
return error;
goto fail;
error = gfs2_glock_nq_init(dip->i_gl, LM_ST_EXCLUSIVE, 0, ghs);
if (error)
@ -641,7 +641,7 @@ static int gfs2_create_inode(struct inode *dir, struct dentry *dentry,
goto fail_gunlock;
ip = GFS2_I(inode);
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
goto fail_free_acls;
@ -776,6 +776,7 @@ fail_gunlock2:
clear_bit(GLF_INODE_CREATING, &io_gl->gl_flags);
gfs2_glock_put(io_gl);
fail_free_inode:
gfs2_qa_put(ip);
if (ip->i_gl) {
glock_clear_object(ip->i_gl, ip);
gfs2_glock_put(ip->i_gl);
@ -798,6 +799,7 @@ fail_gunlock:
if (gfs2_holder_initialized(ghs + 1))
gfs2_glock_dq_uninit(ghs + 1);
fail:
gfs2_qa_put(dip);
return error;
}
@ -899,7 +901,7 @@ static int gfs2_link(struct dentry *old_dentry, struct inode *dir,
if (S_ISDIR(inode->i_mode))
return -EPERM;
error = gfs2_qa_alloc(dip);
error = gfs2_qa_get(dip);
if (error)
return error;
@ -1002,6 +1004,7 @@ out_gunlock:
out_child:
gfs2_glock_dq(ghs);
out_parent:
gfs2_qa_put(ip);
gfs2_holder_uninit(ghs);
gfs2_holder_uninit(ghs + 1);
return error;
@ -1362,7 +1365,7 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry,
if (error)
return error;
error = gfs2_qa_alloc(ndip);
error = gfs2_qa_get(ndip);
if (error)
return error;
@ -1562,6 +1565,7 @@ out_gunlock_r:
if (gfs2_holder_initialized(&r_gh))
gfs2_glock_dq_uninit(&r_gh);
out:
gfs2_qa_put(ndip);
return error;
}
@ -1873,10 +1877,9 @@ static int setattr_chown(struct inode *inode, struct iattr *attr)
ouid = nuid = NO_UID_QUOTA_CHANGE;
if (!(attr->ia_valid & ATTR_GID) || gid_eq(ogid, ngid))
ogid = ngid = NO_GID_QUOTA_CHANGE;
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
goto out;
return error;
error = gfs2_rindex_update(sdp);
if (error)
@ -1914,6 +1917,7 @@ out_end_trans:
out_gunlock_q:
gfs2_quota_unlock(ip);
out:
gfs2_qa_put(ip);
return error;
}
@ -1935,21 +1939,21 @@ static int gfs2_setattr(struct dentry *dentry, struct iattr *attr)
struct gfs2_holder i_gh;
int error;
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
return error;
error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &i_gh);
if (error)
return error;
goto out;
error = -EPERM;
if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
goto out;
goto error;
error = setattr_prepare(dentry, attr);
if (error)
goto out;
goto error;
if (attr->ia_valid & ATTR_SIZE)
error = gfs2_setattr_size(inode, attr->ia_size);
@ -1961,10 +1965,12 @@ static int gfs2_setattr(struct dentry *dentry, struct iattr *attr)
error = posix_acl_chmod(inode, inode->i_mode);
}
out:
error:
if (!error)
mark_inode_dirty(inode);
gfs2_glock_dq_uninit(&i_gh);
out:
gfs2_qa_put(ip);
return error;
}

View File

@ -525,11 +525,11 @@ static void qdsb_put(struct gfs2_quota_data *qd)
}
/**
* gfs2_qa_alloc - make sure we have a quota allocations data structure,
* if necessary
* gfs2_qa_get - make sure we have a quota allocations data structure,
* if necessary
* @ip: the inode for this reservation
*/
int gfs2_qa_alloc(struct gfs2_inode *ip)
int gfs2_qa_get(struct gfs2_inode *ip)
{
int error = 0;
struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode);
@ -540,17 +540,21 @@ int gfs2_qa_alloc(struct gfs2_inode *ip)
down_write(&ip->i_rw_mutex);
if (ip->i_qadata == NULL) {
ip->i_qadata = kmem_cache_zalloc(gfs2_qadata_cachep, GFP_NOFS);
if (!ip->i_qadata)
if (!ip->i_qadata) {
error = -ENOMEM;
goto out;
}
}
ip->i_qadata->qa_ref++;
out:
up_write(&ip->i_rw_mutex);
return error;
}
void gfs2_qa_delete(struct gfs2_inode *ip, atomic_t *wcount)
void gfs2_qa_put(struct gfs2_inode *ip)
{
down_write(&ip->i_rw_mutex);
if (ip->i_qadata && ((wcount == NULL) || (atomic_read(wcount) <= 1))) {
if (ip->i_qadata && --ip->i_qadata->qa_ref == 0) {
kmem_cache_free(gfs2_qadata_cachep, ip->i_qadata);
ip->i_qadata = NULL;
}
@ -566,27 +570,27 @@ int gfs2_quota_hold(struct gfs2_inode *ip, kuid_t uid, kgid_t gid)
if (sdp->sd_args.ar_quota == GFS2_QUOTA_OFF)
return 0;
if (ip->i_qadata == NULL) {
error = gfs2_qa_alloc(ip);
if (error)
return error;
}
error = gfs2_qa_get(ip);
if (error)
return error;
qd = ip->i_qadata->qa_qd;
if (gfs2_assert_warn(sdp, !ip->i_qadata->qa_qd_num) ||
gfs2_assert_warn(sdp, !test_bit(GIF_QD_LOCKED, &ip->i_flags)))
return -EIO;
gfs2_assert_warn(sdp, !test_bit(GIF_QD_LOCKED, &ip->i_flags))) {
error = -EIO;
goto out;
}
error = qdsb_get(sdp, make_kqid_uid(ip->i_inode.i_uid), qd);
if (error)
goto out;
goto out_unhold;
ip->i_qadata->qa_qd_num++;
qd++;
error = qdsb_get(sdp, make_kqid_gid(ip->i_inode.i_gid), qd);
if (error)
goto out;
goto out_unhold;
ip->i_qadata->qa_qd_num++;
qd++;
@ -594,7 +598,7 @@ int gfs2_quota_hold(struct gfs2_inode *ip, kuid_t uid, kgid_t gid)
!uid_eq(uid, ip->i_inode.i_uid)) {
error = qdsb_get(sdp, make_kqid_uid(uid), qd);
if (error)
goto out;
goto out_unhold;
ip->i_qadata->qa_qd_num++;
qd++;
}
@ -603,14 +607,15 @@ int gfs2_quota_hold(struct gfs2_inode *ip, kuid_t uid, kgid_t gid)
!gid_eq(gid, ip->i_inode.i_gid)) {
error = qdsb_get(sdp, make_kqid_gid(gid), qd);
if (error)
goto out;
goto out_unhold;
ip->i_qadata->qa_qd_num++;
qd++;
}
out:
out_unhold:
if (error)
gfs2_quota_unhold(ip);
out:
return error;
}
@ -621,6 +626,7 @@ void gfs2_quota_unhold(struct gfs2_inode *ip)
if (ip->i_qadata == NULL)
return;
gfs2_assert_warn(sdp, !test_bit(GIF_QD_LOCKED, &ip->i_flags));
for (x = 0; x < ip->i_qadata->qa_qd_num; x++) {
@ -628,6 +634,7 @@ void gfs2_quota_unhold(struct gfs2_inode *ip)
ip->i_qadata->qa_qd[x] = NULL;
}
ip->i_qadata->qa_qd_num = 0;
gfs2_qa_put(ip);
}
static int sort_qd(const void *a, const void *b)
@ -876,7 +883,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda)
unsigned int nalloc = 0, blocks;
int error;
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
return error;
@ -884,8 +891,10 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda)
&data_blocks, &ind_blocks);
ghs = kmalloc_array(num_qd, sizeof(struct gfs2_holder), GFP_NOFS);
if (!ghs)
return -ENOMEM;
if (!ghs) {
error = -ENOMEM;
goto out;
}
sort(qda, num_qd, sizeof(struct gfs2_quota_data *), sort_qd, NULL);
inode_lock(&ip->i_inode);
@ -893,12 +902,12 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda)
error = gfs2_glock_nq_init(qda[qx]->qd_gl, LM_ST_EXCLUSIVE,
GL_NOCACHE, &ghs[qx]);
if (error)
goto out;
goto out_dq;
}
error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &i_gh);
if (error)
goto out;
goto out_dq;
for (x = 0; x < num_qd; x++) {
offset = qd2offset(qda[x]);
@ -950,13 +959,15 @@ out_ipres:
gfs2_inplace_release(ip);
out_alloc:
gfs2_glock_dq_uninit(&i_gh);
out:
out_dq:
while (qx--)
gfs2_glock_dq_uninit(&ghs[qx]);
inode_unlock(&ip->i_inode);
kfree(ghs);
gfs2_log_flush(ip->i_gl->gl_name.ln_sbd, ip->i_gl,
GFS2_LOG_HEAD_FLUSH_NORMAL | GFS2_LFC_DO_SYNC);
out:
gfs2_qa_put(ip);
return error;
}
@ -1259,6 +1270,7 @@ void gfs2_quota_change(struct gfs2_inode *ip, s64 change,
if (ip->i_diskflags & GFS2_DIF_SYSTEM)
return;
BUG_ON(ip->i_qadata->qa_ref <= 0);
for (x = 0; x < ip->i_qadata->qa_qd_num; x++) {
qd = ip->i_qadata->qa_qd[x];
@ -1677,7 +1689,7 @@ static int gfs2_set_dqblk(struct super_block *sb, struct kqid qid,
if (error)
return error;
error = gfs2_qa_alloc(ip);
error = gfs2_qa_get(ip);
if (error)
goto out_put;
@ -1746,6 +1758,7 @@ out_i:
out_q:
gfs2_glock_dq_uninit(&q_gh);
out_unlockput:
gfs2_qa_put(ip);
inode_unlock(&ip->i_inode);
out_put:
qd_put(qd);

View File

@ -15,8 +15,8 @@ struct gfs2_sbd;
#define NO_UID_QUOTA_CHANGE INVALID_UID
#define NO_GID_QUOTA_CHANGE INVALID_GID
extern int gfs2_qa_alloc(struct gfs2_inode *ip);
extern void gfs2_qa_delete(struct gfs2_inode *ip, atomic_t *wcount);
extern int gfs2_qa_get(struct gfs2_inode *ip);
extern void gfs2_qa_put(struct gfs2_inode *ip);
extern int gfs2_quota_hold(struct gfs2_inode *ip, kuid_t uid, kgid_t gid);
extern void gfs2_quota_unhold(struct gfs2_inode *ip);

View File

@ -673,7 +673,7 @@ void gfs2_rsqa_delete(struct gfs2_inode *ip, atomic_t *wcount)
if ((wcount == NULL) || (atomic_read(wcount) <= 1))
gfs2_rs_deltree(&ip->i_res);
up_write(&ip->i_rw_mutex);
gfs2_qa_delete(ip, wcount);
gfs2_qa_put(ip);
}
/**

View File

@ -1401,6 +1401,8 @@ out_unlock:
fs_warn(sdp, "gfs2_evict_inode: %d\n", error);
out:
truncate_inode_pages_final(&inode->i_data);
if (ip->i_qadata)
gfs2_assert_warn(sdp, ip->i_qadata->qa_ref == 0);
gfs2_rsqa_delete(ip, NULL);
gfs2_ordered_del_inode(ip);
clear_inode(inode);

View File

@ -1222,7 +1222,7 @@ static int gfs2_xattr_set(const struct xattr_handler *handler,
struct gfs2_holder gh;
int ret;
ret = gfs2_qa_alloc(ip);
ret = gfs2_qa_get(ip);
if (ret)
return ret;
@ -1231,15 +1231,19 @@ static int gfs2_xattr_set(const struct xattr_handler *handler,
if (!gfs2_glock_is_locked_by_me(ip->i_gl)) {
ret = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, &gh);
if (ret)
return ret;
goto out;
} else {
if (WARN_ON_ONCE(ip->i_gl->gl_state != LM_ST_EXCLUSIVE))
return -EIO;
if (WARN_ON_ONCE(ip->i_gl->gl_state != LM_ST_EXCLUSIVE)) {
ret = -EIO;
goto out;
}
gfs2_holder_mark_uninitialized(&gh);
}
ret = __gfs2_xattr_set(inode, name, value, size, flags, handler->flags);
if (gfs2_holder_initialized(&gh))
gfs2_glock_dq_uninit(&gh);
out:
gfs2_qa_put(ip);
return ret;
}