Merge: gfs2: kernel BUG at fs/gfs2/lops.c:135
MR: https://gitlab.com/redhat/centos-stream/src/kernel/centos-stream-9/-/merge_requests/3454 Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=2224067 This merge requests is meant to fix a bunch of bugs related to quotas in gfs2. Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com> Approved-by: Andrew Price <anprice@redhat.com> Approved-by: Abhi Das <adas@redhat.com> Signed-off-by: Scott Weaver <scweaver@redhat.com>
This commit is contained in:
commit
deca267691
|
@ -176,7 +176,7 @@ void gfs2_glock_free(struct gfs2_glock *gl)
|
|||
wake_up_glock(gl);
|
||||
call_rcu(&gl->gl_rcu, gfs2_glock_dealloc);
|
||||
if (atomic_dec_and_test(&sdp->sd_glock_disposal))
|
||||
wake_up(&sdp->sd_glock_wait);
|
||||
wake_up(&sdp->sd_kill_wait);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1233,7 +1233,7 @@ int gfs2_glock_get(struct gfs2_sbd *sdp, u64 number,
|
|||
out_free:
|
||||
gfs2_glock_dealloc(&gl->gl_rcu);
|
||||
if (atomic_dec_and_test(&sdp->sd_glock_disposal))
|
||||
wake_up(&sdp->sd_glock_wait);
|
||||
wake_up(&sdp->sd_kill_wait);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
|
@ -2197,7 +2197,7 @@ void gfs2_gl_hash_clear(struct gfs2_sbd *sdp)
|
|||
flush_workqueue(glock_workqueue);
|
||||
glock_hash_walk(clear_glock, sdp);
|
||||
flush_workqueue(glock_workqueue);
|
||||
wait_event_timeout(sdp->sd_glock_wait,
|
||||
wait_event_timeout(sdp->sd_kill_wait,
|
||||
atomic_read(&sdp->sd_glock_disposal) == 0,
|
||||
HZ * 600);
|
||||
glock_hash_walk(dump_glock_func, sdp);
|
||||
|
|
|
@ -537,6 +537,7 @@ struct gfs2_statfs_change_host {
|
|||
#define GFS2_QUOTA_OFF 0
|
||||
#define GFS2_QUOTA_ACCOUNT 1
|
||||
#define GFS2_QUOTA_ON 2
|
||||
#define GFS2_QUOTA_QUIET 3 /* on but not complaining */
|
||||
|
||||
#define GFS2_DATA_DEFAULT GFS2_DATA_ORDERED
|
||||
#define GFS2_DATA_WRITEBACK 1
|
||||
|
@ -716,7 +717,7 @@ struct gfs2_sbd {
|
|||
struct gfs2_glock *sd_rename_gl;
|
||||
struct gfs2_glock *sd_freeze_gl;
|
||||
struct work_struct sd_freeze_work;
|
||||
wait_queue_head_t sd_glock_wait;
|
||||
wait_queue_head_t sd_kill_wait;
|
||||
wait_queue_head_t sd_async_glock_wait;
|
||||
atomic_t sd_glock_disposal;
|
||||
struct completion sd_locking_init;
|
||||
|
|
|
@ -87,7 +87,7 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb)
|
|||
set_bit(SDF_NOJOURNALID, &sdp->sd_flags);
|
||||
gfs2_tune_init(&sdp->sd_tune);
|
||||
|
||||
init_waitqueue_head(&sdp->sd_glock_wait);
|
||||
init_waitqueue_head(&sdp->sd_kill_wait);
|
||||
init_waitqueue_head(&sdp->sd_async_glock_wait);
|
||||
atomic_set(&sdp->sd_glock_disposal, 0);
|
||||
init_completion(&sdp->sd_locking_init);
|
||||
|
@ -1384,6 +1384,7 @@ static const struct constant_table gfs2_param_quota[] = {
|
|||
{"off", GFS2_QUOTA_OFF},
|
||||
{"account", GFS2_QUOTA_ACCOUNT},
|
||||
{"on", GFS2_QUOTA_ON},
|
||||
{"quiet", GFS2_QUOTA_QUIET},
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
165
fs/gfs2/quota.c
165
fs/gfs2/quota.c
|
@ -75,6 +75,9 @@
|
|||
#define GFS2_QD_HASH_SIZE BIT(GFS2_QD_HASH_SHIFT)
|
||||
#define GFS2_QD_HASH_MASK (GFS2_QD_HASH_SIZE - 1)
|
||||
|
||||
#define QC_CHANGE 0
|
||||
#define QC_SYNC 1
|
||||
|
||||
/* Lock order: qd_lock -> bucket lock -> qd->lockref.lock -> lru lock */
|
||||
/* -> sd_bitmap_lock */
|
||||
static DEFINE_SPINLOCK(qd_lock);
|
||||
|
@ -106,38 +109,42 @@ static inline void spin_unlock_bucket(unsigned int hash)
|
|||
static void gfs2_qd_dealloc(struct rcu_head *rcu)
|
||||
{
|
||||
struct gfs2_quota_data *qd = container_of(rcu, struct gfs2_quota_data, qd_rcu);
|
||||
struct gfs2_sbd *sdp = qd->qd_sbd;
|
||||
|
||||
kmem_cache_free(gfs2_quotad_cachep, qd);
|
||||
if (atomic_dec_and_test(&sdp->sd_quota_count))
|
||||
wake_up(&sdp->sd_kill_wait);
|
||||
}
|
||||
|
||||
static void gfs2_qd_dispose(struct list_head *list)
|
||||
static void gfs2_qd_dispose(struct gfs2_quota_data *qd)
|
||||
{
|
||||
struct gfs2_sbd *sdp = qd->qd_sbd;
|
||||
|
||||
spin_lock(&qd_lock);
|
||||
list_del(&qd->qd_list);
|
||||
spin_unlock(&qd_lock);
|
||||
|
||||
spin_lock_bucket(qd->qd_hash);
|
||||
hlist_bl_del_rcu(&qd->qd_hlist);
|
||||
spin_unlock_bucket(qd->qd_hash);
|
||||
|
||||
gfs2_assert_warn(sdp, !qd->qd_change);
|
||||
gfs2_assert_warn(sdp, !qd->qd_slot_count);
|
||||
gfs2_assert_warn(sdp, !qd->qd_bh_count);
|
||||
|
||||
gfs2_glock_put(qd->qd_gl);
|
||||
call_rcu(&qd->qd_rcu, gfs2_qd_dealloc);
|
||||
}
|
||||
|
||||
static void gfs2_qd_list_dispose(struct list_head *list)
|
||||
{
|
||||
struct gfs2_quota_data *qd;
|
||||
struct gfs2_sbd *sdp;
|
||||
|
||||
while (!list_empty(list)) {
|
||||
qd = list_first_entry(list, struct gfs2_quota_data, qd_lru);
|
||||
sdp = qd->qd_gl->gl_name.ln_sbd;
|
||||
|
||||
list_del(&qd->qd_lru);
|
||||
|
||||
/* Free from the filesystem-specific list */
|
||||
spin_lock(&qd_lock);
|
||||
list_del(&qd->qd_list);
|
||||
spin_unlock(&qd_lock);
|
||||
|
||||
spin_lock_bucket(qd->qd_hash);
|
||||
hlist_bl_del_rcu(&qd->qd_hlist);
|
||||
spin_unlock_bucket(qd->qd_hash);
|
||||
|
||||
gfs2_assert_warn(sdp, !qd->qd_change);
|
||||
gfs2_assert_warn(sdp, !qd->qd_slot_count);
|
||||
gfs2_assert_warn(sdp, !qd->qd_bh_count);
|
||||
|
||||
gfs2_glock_put(qd->qd_gl);
|
||||
atomic_dec(&sdp->sd_quota_count);
|
||||
|
||||
/* Delete it from the common reclaim list */
|
||||
call_rcu(&qd->qd_rcu, gfs2_qd_dealloc);
|
||||
gfs2_qd_dispose(qd);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,18 +153,22 @@ static enum lru_status gfs2_qd_isolate(struct list_head *item,
|
|||
struct list_lru_one *lru, spinlock_t *lru_lock, void *arg)
|
||||
{
|
||||
struct list_head *dispose = arg;
|
||||
struct gfs2_quota_data *qd = list_entry(item, struct gfs2_quota_data, qd_lru);
|
||||
struct gfs2_quota_data *qd =
|
||||
list_entry(item, struct gfs2_quota_data, qd_lru);
|
||||
enum lru_status status;
|
||||
|
||||
if (!spin_trylock(&qd->qd_lockref.lock))
|
||||
return LRU_SKIP;
|
||||
|
||||
status = LRU_SKIP;
|
||||
if (qd->qd_lockref.count == 0) {
|
||||
lockref_mark_dead(&qd->qd_lockref);
|
||||
list_lru_isolate_move(lru, &qd->qd_lru, dispose);
|
||||
status = LRU_REMOVED;
|
||||
}
|
||||
|
||||
spin_unlock(&qd->qd_lockref.lock);
|
||||
return LRU_REMOVED;
|
||||
return status;
|
||||
}
|
||||
|
||||
static unsigned long gfs2_qd_shrink_scan(struct shrinker *shrink,
|
||||
|
@ -172,7 +183,7 @@ static unsigned long gfs2_qd_shrink_scan(struct shrinker *shrink,
|
|||
freed = list_lru_shrink_walk(&gfs2_qd_lru, sc,
|
||||
gfs2_qd_isolate, &dispose);
|
||||
|
||||
gfs2_qd_dispose(&dispose);
|
||||
gfs2_qd_list_dispose(&dispose);
|
||||
|
||||
return freed;
|
||||
}
|
||||
|
@ -218,7 +229,7 @@ static struct gfs2_quota_data *qd_alloc(unsigned hash, struct gfs2_sbd *sdp, str
|
|||
return NULL;
|
||||
|
||||
qd->qd_sbd = sdp;
|
||||
qd->qd_lockref.count = 1;
|
||||
qd->qd_lockref.count = 0;
|
||||
spin_lock_init(&qd->qd_lockref.lock);
|
||||
qd->qd_id = qid;
|
||||
qd->qd_slot = -1;
|
||||
|
@ -280,6 +291,7 @@ static int qd_get(struct gfs2_sbd *sdp, struct kqid qid,
|
|||
spin_lock_bucket(hash);
|
||||
*qdp = qd = gfs2_qd_search_bucket(hash, sdp, qid);
|
||||
if (qd == NULL) {
|
||||
new_qd->qd_lockref.count++;
|
||||
*qdp = new_qd;
|
||||
list_add(&new_qd->qd_list, &sdp->sd_quota_list);
|
||||
hlist_bl_add_head_rcu(&new_qd->qd_hlist, &qd_hash_table[hash]);
|
||||
|
@ -306,13 +318,24 @@ static void qd_hold(struct gfs2_quota_data *qd)
|
|||
|
||||
static void qd_put(struct gfs2_quota_data *qd)
|
||||
{
|
||||
struct gfs2_sbd *sdp;
|
||||
|
||||
if (lockref_put_or_lock(&qd->qd_lockref))
|
||||
return;
|
||||
|
||||
BUG_ON(__lockref_is_dead(&qd->qd_lockref));
|
||||
sdp = qd->qd_sbd;
|
||||
if (unlikely(!test_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags))) {
|
||||
lockref_mark_dead(&qd->qd_lockref);
|
||||
spin_unlock(&qd->qd_lockref.lock);
|
||||
|
||||
gfs2_qd_dispose(qd);
|
||||
return;
|
||||
}
|
||||
|
||||
qd->qd_lockref.count = 0;
|
||||
list_lru_add(&gfs2_qd_lru, &qd->qd_lru);
|
||||
spin_unlock(&qd->qd_lockref.lock);
|
||||
|
||||
}
|
||||
|
||||
static int slot_get(struct gfs2_quota_data *qd)
|
||||
|
@ -431,6 +454,17 @@ static int qd_check_sync(struct gfs2_sbd *sdp, struct gfs2_quota_data *qd,
|
|||
(sync_gen && (qd->qd_sync_gen >= *sync_gen)))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If qd_change is 0 it means a pending quota change was negated.
|
||||
* We should not sync it, but we still have a qd reference and slot
|
||||
* reference taken by gfs2_quota_change -> do_qc that need to be put.
|
||||
*/
|
||||
if (!qd->qd_change && test_and_clear_bit(QDF_CHANGE, &qd->qd_flags)) {
|
||||
slot_put(qd);
|
||||
qd_put(qd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!lockref_get_not_dead(&qd->qd_lockref))
|
||||
return 0;
|
||||
|
||||
|
@ -466,7 +500,6 @@ static int qd_fish(struct gfs2_sbd *sdp, struct gfs2_quota_data **qdp)
|
|||
spin_unlock(&qd_lock);
|
||||
|
||||
if (qd) {
|
||||
gfs2_assert_warn(sdp, qd->qd_change_sync);
|
||||
error = bh_get(qd);
|
||||
if (error) {
|
||||
clear_bit(QDF_LOCKED, &qd->qd_flags);
|
||||
|
@ -649,7 +682,7 @@ static int sort_qd(const void *a, const void *b)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void do_qc(struct gfs2_quota_data *qd, s64 change)
|
||||
static void do_qc(struct gfs2_quota_data *qd, s64 change, int qc_type)
|
||||
{
|
||||
struct gfs2_sbd *sdp = qd->qd_gl->gl_name.ln_sbd;
|
||||
struct gfs2_inode *ip = GFS2_I(sdp->sd_qc_inode);
|
||||
|
@ -674,16 +707,18 @@ static void do_qc(struct gfs2_quota_data *qd, s64 change)
|
|||
qd->qd_change = x;
|
||||
spin_unlock(&qd_lock);
|
||||
|
||||
if (!x) {
|
||||
if (qc_type == QC_CHANGE) {
|
||||
if (!test_and_set_bit(QDF_CHANGE, &qd->qd_flags)) {
|
||||
qd_hold(qd);
|
||||
slot_hold(qd);
|
||||
}
|
||||
} else {
|
||||
gfs2_assert_warn(sdp, test_bit(QDF_CHANGE, &qd->qd_flags));
|
||||
clear_bit(QDF_CHANGE, &qd->qd_flags);
|
||||
qc->qc_flags = 0;
|
||||
qc->qc_id = 0;
|
||||
slot_put(qd);
|
||||
qd_put(qd);
|
||||
} else if (!test_and_set_bit(QDF_CHANGE, &qd->qd_flags)) {
|
||||
qd_hold(qd);
|
||||
slot_hold(qd);
|
||||
}
|
||||
|
||||
if (change < 0) /* Reset quiet flag if we freed some blocks */
|
||||
|
@ -943,7 +978,7 @@ static int do_sync(unsigned int num_qd, struct gfs2_quota_data **qda)
|
|||
if (error)
|
||||
goto out_end_trans;
|
||||
|
||||
do_qc(qd, -qd->qd_change_sync);
|
||||
do_qc(qd, -qd->qd_change_sync, QC_SYNC);
|
||||
set_bit(QDF_REFRESH, &qd->qd_flags);
|
||||
}
|
||||
|
||||
|
@ -1047,7 +1082,8 @@ int gfs2_quota_lock(struct gfs2_inode *ip, kuid_t uid, kgid_t gid)
|
|||
u32 x;
|
||||
int error = 0;
|
||||
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_ON)
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_ON &&
|
||||
sdp->sd_args.ar_quota != GFS2_QUOTA_QUIET)
|
||||
return 0;
|
||||
|
||||
error = gfs2_quota_hold(ip, uid, gid);
|
||||
|
@ -1166,10 +1202,11 @@ static int print_message(struct gfs2_quota_data *qd, char *type)
|
|||
{
|
||||
struct gfs2_sbd *sdp = qd->qd_gl->gl_name.ln_sbd;
|
||||
|
||||
fs_info(sdp, "quota %s for %s %u\n",
|
||||
type,
|
||||
(qd->qd_id.type == USRQUOTA) ? "user" : "group",
|
||||
from_kqid(&init_user_ns, qd->qd_id));
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_QUIET)
|
||||
fs_info(sdp, "quota %s for %s %u\n",
|
||||
type,
|
||||
(qd->qd_id.type == USRQUOTA) ? "user" : "group",
|
||||
from_kqid(&init_user_ns, qd->qd_id));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1255,7 +1292,8 @@ void gfs2_quota_change(struct gfs2_inode *ip, s64 change,
|
|||
u32 x;
|
||||
struct gfs2_sbd *sdp = GFS2_SB(&ip->i_inode);
|
||||
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_ON ||
|
||||
if ((sdp->sd_args.ar_quota != GFS2_QUOTA_ON &&
|
||||
sdp->sd_args.ar_quota != GFS2_QUOTA_QUIET) ||
|
||||
gfs2_assert_warn(sdp, change))
|
||||
return;
|
||||
if (ip->i_diskflags & GFS2_DIF_SYSTEM)
|
||||
|
@ -1269,7 +1307,7 @@ void gfs2_quota_change(struct gfs2_inode *ip, s64 change,
|
|||
|
||||
if (qid_eq(qd->qd_id, make_kqid_uid(uid)) ||
|
||||
qid_eq(qd->qd_id, make_kqid_gid(gid))) {
|
||||
do_qc(qd, change);
|
||||
do_qc(qd, change, QC_CHANGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1441,36 +1479,35 @@ fail:
|
|||
|
||||
void gfs2_quota_cleanup(struct gfs2_sbd *sdp)
|
||||
{
|
||||
struct list_head *head = &sdp->sd_quota_list;
|
||||
struct gfs2_quota_data *qd;
|
||||
LIST_HEAD(dispose);
|
||||
int count;
|
||||
|
||||
BUG_ON(test_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags));
|
||||
|
||||
spin_lock(&qd_lock);
|
||||
while (!list_empty(head)) {
|
||||
qd = list_last_entry(head, struct gfs2_quota_data, qd_list);
|
||||
list_for_each_entry(qd, &sdp->sd_quota_list, qd_list) {
|
||||
spin_lock(&qd->qd_lockref.lock);
|
||||
if (qd->qd_lockref.count != 0) {
|
||||
spin_unlock(&qd->qd_lockref.lock);
|
||||
continue;
|
||||
}
|
||||
lockref_mark_dead(&qd->qd_lockref);
|
||||
spin_unlock(&qd->qd_lockref.lock);
|
||||
|
||||
list_del(&qd->qd_list);
|
||||
|
||||
/* Also remove if this qd exists in the reclaim list */
|
||||
list_lru_del(&gfs2_qd_lru, &qd->qd_lru);
|
||||
atomic_dec(&sdp->sd_quota_count);
|
||||
spin_unlock(&qd_lock);
|
||||
|
||||
spin_lock_bucket(qd->qd_hash);
|
||||
hlist_bl_del_rcu(&qd->qd_hlist);
|
||||
spin_unlock_bucket(qd->qd_hash);
|
||||
|
||||
gfs2_assert_warn(sdp, !qd->qd_change);
|
||||
gfs2_assert_warn(sdp, !qd->qd_slot_count);
|
||||
gfs2_assert_warn(sdp, !qd->qd_bh_count);
|
||||
|
||||
gfs2_glock_put(qd->qd_gl);
|
||||
call_rcu(&qd->qd_rcu, gfs2_qd_dealloc);
|
||||
|
||||
spin_lock(&qd_lock);
|
||||
list_add(&qd->qd_lru, &dispose);
|
||||
}
|
||||
spin_unlock(&qd_lock);
|
||||
|
||||
gfs2_assert_warn(sdp, !atomic_read(&sdp->sd_quota_count));
|
||||
gfs2_qd_list_dispose(&dispose);
|
||||
|
||||
wait_event_timeout(sdp->sd_kill_wait,
|
||||
(count = atomic_read(&sdp->sd_quota_count)) == 0,
|
||||
HZ * 60);
|
||||
|
||||
if (count != 0)
|
||||
fs_err(sdp, "%d left-over quota data objects\n", count);
|
||||
|
||||
kvfree(sdp->sd_quota_bitmap);
|
||||
sdp->sd_quota_bitmap = NULL;
|
||||
|
@ -1566,6 +1603,8 @@ static int gfs2_quota_get_state(struct super_block *sb, struct qc_state *state)
|
|||
memset(state, 0, sizeof(*state));
|
||||
|
||||
switch (sdp->sd_args.ar_quota) {
|
||||
case GFS2_QUOTA_QUIET:
|
||||
fallthrough;
|
||||
case GFS2_QUOTA_ON:
|
||||
state->s_state[USRQUOTA].flags |= QCI_LIMITS_ENFORCED;
|
||||
state->s_state[GRPQUOTA].flags |= QCI_LIMITS_ENFORCED;
|
||||
|
|
|
@ -50,7 +50,8 @@ static inline int gfs2_quota_lock_check(struct gfs2_inode *ip,
|
|||
ret = gfs2_quota_lock(ip, NO_UID_QUOTA_CHANGE, NO_GID_QUOTA_CHANGE);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_ON)
|
||||
if (sdp->sd_args.ar_quota != GFS2_QUOTA_ON &&
|
||||
sdp->sd_args.ar_quota != GFS2_QUOTA_QUIET)
|
||||
return 0;
|
||||
ret = gfs2_quota_check(ip, ip->i_inode.i_uid, ip->i_inode.i_gid, ap);
|
||||
if (ret)
|
||||
|
|
|
@ -1116,6 +1116,9 @@ static int gfs2_show_options(struct seq_file *s, struct dentry *root)
|
|||
case GFS2_QUOTA_ON:
|
||||
state = "on";
|
||||
break;
|
||||
case GFS2_QUOTA_QUIET:
|
||||
state = "quiet";
|
||||
break;
|
||||
default:
|
||||
state = "unknown";
|
||||
break;
|
||||
|
|
Loading…
Reference in New Issue