|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #include "bcachefs.h" | 
|  | #include "btree_update.h" | 
|  | #include "errcode.h" | 
|  | #include "error.h" | 
|  | #include "inode.h" | 
|  | #include "quota.h" | 
|  | #include "snapshot.h" | 
|  | #include "super-io.h" | 
|  |  | 
|  | static const char * const bch2_quota_types[] = { | 
|  | "user", | 
|  | "group", | 
|  | "project", | 
|  | }; | 
|  |  | 
|  | static const char * const bch2_quota_counters[] = { | 
|  | "space", | 
|  | "inodes", | 
|  | }; | 
|  |  | 
|  | static int bch2_sb_quota_validate(struct bch_sb *sb, struct bch_sb_field *f, | 
|  | enum bch_validate_flags flags, struct printbuf *err) | 
|  | { | 
|  | struct bch_sb_field_quota *q = field_to_type(f, quota); | 
|  |  | 
|  | if (vstruct_bytes(&q->field) < sizeof(*q)) { | 
|  | prt_printf(err, "wrong size (got %zu should be %zu)", | 
|  | vstruct_bytes(&q->field), sizeof(*q)); | 
|  | return -BCH_ERR_invalid_sb_quota; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void bch2_sb_quota_to_text(struct printbuf *out, struct bch_sb *sb, | 
|  | struct bch_sb_field *f) | 
|  | { | 
|  | struct bch_sb_field_quota *q = field_to_type(f, quota); | 
|  | unsigned qtyp, counter; | 
|  |  | 
|  | for (qtyp = 0; qtyp < ARRAY_SIZE(q->q); qtyp++) { | 
|  | prt_printf(out, "%s: flags %llx", | 
|  | bch2_quota_types[qtyp], | 
|  | le64_to_cpu(q->q[qtyp].flags)); | 
|  |  | 
|  | for (counter = 0; counter < Q_COUNTERS; counter++) | 
|  | prt_printf(out, " %s timelimit %u warnlimit %u", | 
|  | bch2_quota_counters[counter], | 
|  | le32_to_cpu(q->q[qtyp].c[counter].timelimit), | 
|  | le32_to_cpu(q->q[qtyp].c[counter].warnlimit)); | 
|  |  | 
|  | prt_newline(out); | 
|  | } | 
|  | } | 
|  |  | 
|  | const struct bch_sb_field_ops bch_sb_field_ops_quota = { | 
|  | .validate	= bch2_sb_quota_validate, | 
|  | .to_text	= bch2_sb_quota_to_text, | 
|  | }; | 
|  |  | 
|  | int bch2_quota_validate(struct bch_fs *c, struct bkey_s_c k, | 
|  | enum bch_validate_flags flags) | 
|  | { | 
|  | int ret = 0; | 
|  |  | 
|  | bkey_fsck_err_on(k.k->p.inode >= QTYP_NR, | 
|  | c, quota_type_invalid, | 
|  | "invalid quota type (%llu >= %u)", | 
|  | k.k->p.inode, QTYP_NR); | 
|  | fsck_err: | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void bch2_quota_to_text(struct printbuf *out, struct bch_fs *c, | 
|  | struct bkey_s_c k) | 
|  | { | 
|  | struct bkey_s_c_quota dq = bkey_s_c_to_quota(k); | 
|  | unsigned i; | 
|  |  | 
|  | for (i = 0; i < Q_COUNTERS; i++) | 
|  | prt_printf(out, "%s hardlimit %llu softlimit %llu", | 
|  | bch2_quota_counters[i], | 
|  | le64_to_cpu(dq.v->c[i].hardlimit), | 
|  | le64_to_cpu(dq.v->c[i].softlimit)); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_BCACHEFS_QUOTA | 
|  |  | 
|  | #include <linux/cred.h> | 
|  | #include <linux/fs.h> | 
|  | #include <linux/quota.h> | 
|  |  | 
|  | static void qc_info_to_text(struct printbuf *out, struct qc_info *i) | 
|  | { | 
|  | printbuf_tabstops_reset(out); | 
|  | printbuf_tabstop_push(out, 20); | 
|  |  | 
|  | prt_printf(out, "i_fieldmask\t%x\n",		i->i_fieldmask); | 
|  | prt_printf(out, "i_flags\t%u\n",		i->i_flags); | 
|  | prt_printf(out, "i_spc_timelimit\t%u\n",	i->i_spc_timelimit); | 
|  | prt_printf(out, "i_ino_timelimit\t%u\n",	i->i_ino_timelimit); | 
|  | prt_printf(out, "i_rt_spc_timelimit\t%u\n",	i->i_rt_spc_timelimit); | 
|  | prt_printf(out, "i_spc_warnlimit\t%u\n",	i->i_spc_warnlimit); | 
|  | prt_printf(out, "i_ino_warnlimit\t%u\n",	i->i_ino_warnlimit); | 
|  | prt_printf(out, "i_rt_spc_warnlimit\t%u\n",	i->i_rt_spc_warnlimit); | 
|  | } | 
|  |  | 
|  | static void qc_dqblk_to_text(struct printbuf *out, struct qc_dqblk *q) | 
|  | { | 
|  | printbuf_tabstops_reset(out); | 
|  | printbuf_tabstop_push(out, 20); | 
|  |  | 
|  | prt_printf(out, "d_fieldmask\t%x\n",		q->d_fieldmask); | 
|  | prt_printf(out, "d_spc_hardlimit\t%llu\n",	q->d_spc_hardlimit); | 
|  | prt_printf(out, "d_spc_softlimit\t%llu\n",	q->d_spc_softlimit); | 
|  | prt_printf(out, "d_ino_hardlimit\%llu\n",	q->d_ino_hardlimit); | 
|  | prt_printf(out, "d_ino_softlimit\t%llu\n",	q->d_ino_softlimit); | 
|  | prt_printf(out, "d_space\t%llu\n",		q->d_space); | 
|  | prt_printf(out, "d_ino_count\t%llu\n",		q->d_ino_count); | 
|  | prt_printf(out, "d_ino_timer\t%llu\n",		q->d_ino_timer); | 
|  | prt_printf(out, "d_spc_timer\t%llu\n",		q->d_spc_timer); | 
|  | prt_printf(out, "d_ino_warns\t%i\n",		q->d_ino_warns); | 
|  | prt_printf(out, "d_spc_warns\t%i\n",		q->d_spc_warns); | 
|  | } | 
|  |  | 
|  | static inline unsigned __next_qtype(unsigned i, unsigned qtypes) | 
|  | { | 
|  | qtypes >>= i; | 
|  | return qtypes ? i + __ffs(qtypes) : QTYP_NR; | 
|  | } | 
|  |  | 
|  | #define for_each_set_qtype(_c, _i, _q, _qtypes)				\ | 
|  | for (_i = 0;							\ | 
|  | (_i = __next_qtype(_i, _qtypes),				\ | 
|  | _q = &(_c)->quotas[_i],					\ | 
|  | _i < QTYP_NR);						\ | 
|  | _i++) | 
|  |  | 
|  | static bool ignore_hardlimit(struct bch_memquota_type *q) | 
|  | { | 
|  | if (capable(CAP_SYS_RESOURCE)) | 
|  | return true; | 
|  | #if 0 | 
|  | struct mem_dqinfo *info = &sb_dqopt(dquot->dq_sb)->info[dquot->dq_id.type]; | 
|  |  | 
|  | return capable(CAP_SYS_RESOURCE) && | 
|  | (info->dqi_format->qf_fmt_id != QFMT_VFS_OLD || | 
|  | !(info->dqi_flags & DQF_ROOT_SQUASH)); | 
|  | #endif | 
|  | return false; | 
|  | } | 
|  |  | 
|  | enum quota_msg { | 
|  | SOFTWARN,	/* Softlimit reached */ | 
|  | SOFTLONGWARN,	/* Grace time expired */ | 
|  | HARDWARN,	/* Hardlimit reached */ | 
|  |  | 
|  | HARDBELOW,	/* Usage got below inode hardlimit */ | 
|  | SOFTBELOW,	/* Usage got below inode softlimit */ | 
|  | }; | 
|  |  | 
|  | static int quota_nl[][Q_COUNTERS] = { | 
|  | [HARDWARN][Q_SPC]	= QUOTA_NL_BHARDWARN, | 
|  | [SOFTLONGWARN][Q_SPC]	= QUOTA_NL_BSOFTLONGWARN, | 
|  | [SOFTWARN][Q_SPC]	= QUOTA_NL_BSOFTWARN, | 
|  | [HARDBELOW][Q_SPC]	= QUOTA_NL_BHARDBELOW, | 
|  | [SOFTBELOW][Q_SPC]	= QUOTA_NL_BSOFTBELOW, | 
|  |  | 
|  | [HARDWARN][Q_INO]	= QUOTA_NL_IHARDWARN, | 
|  | [SOFTLONGWARN][Q_INO]	= QUOTA_NL_ISOFTLONGWARN, | 
|  | [SOFTWARN][Q_INO]	= QUOTA_NL_ISOFTWARN, | 
|  | [HARDBELOW][Q_INO]	= QUOTA_NL_IHARDBELOW, | 
|  | [SOFTBELOW][Q_INO]	= QUOTA_NL_ISOFTBELOW, | 
|  | }; | 
|  |  | 
|  | struct quota_msgs { | 
|  | u8		nr; | 
|  | struct { | 
|  | u8	qtype; | 
|  | u8	msg; | 
|  | }		m[QTYP_NR * Q_COUNTERS]; | 
|  | }; | 
|  |  | 
|  | static void prepare_msg(unsigned qtype, | 
|  | enum quota_counters counter, | 
|  | struct quota_msgs *msgs, | 
|  | enum quota_msg msg_type) | 
|  | { | 
|  | BUG_ON(msgs->nr >= ARRAY_SIZE(msgs->m)); | 
|  |  | 
|  | msgs->m[msgs->nr].qtype	= qtype; | 
|  | msgs->m[msgs->nr].msg	= quota_nl[msg_type][counter]; | 
|  | msgs->nr++; | 
|  | } | 
|  |  | 
|  | static void prepare_warning(struct memquota_counter *qc, | 
|  | unsigned qtype, | 
|  | enum quota_counters counter, | 
|  | struct quota_msgs *msgs, | 
|  | enum quota_msg msg_type) | 
|  | { | 
|  | if (qc->warning_issued & (1 << msg_type)) | 
|  | return; | 
|  |  | 
|  | prepare_msg(qtype, counter, msgs, msg_type); | 
|  | } | 
|  |  | 
|  | static void flush_warnings(struct bch_qid qid, | 
|  | struct super_block *sb, | 
|  | struct quota_msgs *msgs) | 
|  | { | 
|  | unsigned i; | 
|  |  | 
|  | for (i = 0; i < msgs->nr; i++) | 
|  | quota_send_warning(make_kqid(&init_user_ns, msgs->m[i].qtype, qid.q[i]), | 
|  | sb->s_dev, msgs->m[i].msg); | 
|  | } | 
|  |  | 
|  | static int bch2_quota_check_limit(struct bch_fs *c, | 
|  | unsigned qtype, | 
|  | struct bch_memquota *mq, | 
|  | struct quota_msgs *msgs, | 
|  | enum quota_counters counter, | 
|  | s64 v, | 
|  | enum quota_acct_mode mode) | 
|  | { | 
|  | struct bch_memquota_type *q = &c->quotas[qtype]; | 
|  | struct memquota_counter *qc = &mq->c[counter]; | 
|  | u64 n = qc->v + v; | 
|  |  | 
|  | BUG_ON((s64) n < 0); | 
|  |  | 
|  | if (mode == KEY_TYPE_QUOTA_NOCHECK) | 
|  | return 0; | 
|  |  | 
|  | if (v <= 0) { | 
|  | if (n < qc->hardlimit && | 
|  | (qc->warning_issued & (1 << HARDWARN))) { | 
|  | qc->warning_issued &= ~(1 << HARDWARN); | 
|  | prepare_msg(qtype, counter, msgs, HARDBELOW); | 
|  | } | 
|  |  | 
|  | if (n < qc->softlimit && | 
|  | (qc->warning_issued & (1 << SOFTWARN))) { | 
|  | qc->warning_issued &= ~(1 << SOFTWARN); | 
|  | prepare_msg(qtype, counter, msgs, SOFTBELOW); | 
|  | } | 
|  |  | 
|  | qc->warning_issued = 0; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (qc->hardlimit && | 
|  | qc->hardlimit < n && | 
|  | !ignore_hardlimit(q)) { | 
|  | prepare_warning(qc, qtype, counter, msgs, HARDWARN); | 
|  | return -EDQUOT; | 
|  | } | 
|  |  | 
|  | if (qc->softlimit && | 
|  | qc->softlimit < n) { | 
|  | if (qc->timer == 0) { | 
|  | qc->timer = ktime_get_real_seconds() + q->limits[counter].timelimit; | 
|  | prepare_warning(qc, qtype, counter, msgs, SOFTWARN); | 
|  | } else if (ktime_get_real_seconds() >= qc->timer && | 
|  | !ignore_hardlimit(q)) { | 
|  | prepare_warning(qc, qtype, counter, msgs, SOFTLONGWARN); | 
|  | return -EDQUOT; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int bch2_quota_acct(struct bch_fs *c, struct bch_qid qid, | 
|  | enum quota_counters counter, s64 v, | 
|  | enum quota_acct_mode mode) | 
|  | { | 
|  | unsigned qtypes = enabled_qtypes(c); | 
|  | struct bch_memquota_type *q; | 
|  | struct bch_memquota *mq[QTYP_NR]; | 
|  | struct quota_msgs msgs; | 
|  | unsigned i; | 
|  | int ret = 0; | 
|  |  | 
|  | memset(&msgs, 0, sizeof(msgs)); | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) { | 
|  | mq[i] = genradix_ptr_alloc(&q->table, qid.q[i], GFP_KERNEL); | 
|  | if (!mq[i]) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) | 
|  | mutex_lock_nested(&q->lock, i); | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) { | 
|  | ret = bch2_quota_check_limit(c, i, mq[i], &msgs, counter, v, mode); | 
|  | if (ret) | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) | 
|  | mq[i]->c[counter].v += v; | 
|  | err: | 
|  | for_each_set_qtype(c, i, q, qtypes) | 
|  | mutex_unlock(&q->lock); | 
|  |  | 
|  | flush_warnings(qid, c->vfs_sb, &msgs); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void __bch2_quota_transfer(struct bch_memquota *src_q, | 
|  | struct bch_memquota *dst_q, | 
|  | enum quota_counters counter, s64 v) | 
|  | { | 
|  | BUG_ON(v > src_q->c[counter].v); | 
|  | BUG_ON(v + dst_q->c[counter].v < v); | 
|  |  | 
|  | src_q->c[counter].v -= v; | 
|  | dst_q->c[counter].v += v; | 
|  | } | 
|  |  | 
|  | int bch2_quota_transfer(struct bch_fs *c, unsigned qtypes, | 
|  | struct bch_qid dst, | 
|  | struct bch_qid src, u64 space, | 
|  | enum quota_acct_mode mode) | 
|  | { | 
|  | struct bch_memquota_type *q; | 
|  | struct bch_memquota *src_q[3], *dst_q[3]; | 
|  | struct quota_msgs msgs; | 
|  | unsigned i; | 
|  | int ret = 0; | 
|  |  | 
|  | qtypes &= enabled_qtypes(c); | 
|  |  | 
|  | memset(&msgs, 0, sizeof(msgs)); | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) { | 
|  | src_q[i] = genradix_ptr_alloc(&q->table, src.q[i], GFP_KERNEL); | 
|  | dst_q[i] = genradix_ptr_alloc(&q->table, dst.q[i], GFP_KERNEL); | 
|  | if (!src_q[i] || !dst_q[i]) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) | 
|  | mutex_lock_nested(&q->lock, i); | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) { | 
|  | ret = bch2_quota_check_limit(c, i, dst_q[i], &msgs, Q_SPC, | 
|  | dst_q[i]->c[Q_SPC].v + space, | 
|  | mode); | 
|  | if (ret) | 
|  | goto err; | 
|  |  | 
|  | ret = bch2_quota_check_limit(c, i, dst_q[i], &msgs, Q_INO, | 
|  | dst_q[i]->c[Q_INO].v + 1, | 
|  | mode); | 
|  | if (ret) | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | for_each_set_qtype(c, i, q, qtypes) { | 
|  | __bch2_quota_transfer(src_q[i], dst_q[i], Q_SPC, space); | 
|  | __bch2_quota_transfer(src_q[i], dst_q[i], Q_INO, 1); | 
|  | } | 
|  |  | 
|  | err: | 
|  | for_each_set_qtype(c, i, q, qtypes) | 
|  | mutex_unlock(&q->lock); | 
|  |  | 
|  | flush_warnings(dst, c->vfs_sb, &msgs); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int __bch2_quota_set(struct bch_fs *c, struct bkey_s_c k, | 
|  | struct qc_dqblk *qdq) | 
|  | { | 
|  | struct bkey_s_c_quota dq; | 
|  | struct bch_memquota_type *q; | 
|  | struct bch_memquota *mq; | 
|  | unsigned i; | 
|  |  | 
|  | BUG_ON(k.k->p.inode >= QTYP_NR); | 
|  |  | 
|  | if (!((1U << k.k->p.inode) & enabled_qtypes(c))) | 
|  | return 0; | 
|  |  | 
|  | switch (k.k->type) { | 
|  | case KEY_TYPE_quota: | 
|  | dq = bkey_s_c_to_quota(k); | 
|  | q = &c->quotas[k.k->p.inode]; | 
|  |  | 
|  | mutex_lock(&q->lock); | 
|  | mq = genradix_ptr_alloc(&q->table, k.k->p.offset, GFP_KERNEL); | 
|  | if (!mq) { | 
|  | mutex_unlock(&q->lock); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < Q_COUNTERS; i++) { | 
|  | mq->c[i].hardlimit = le64_to_cpu(dq.v->c[i].hardlimit); | 
|  | mq->c[i].softlimit = le64_to_cpu(dq.v->c[i].softlimit); | 
|  | } | 
|  |  | 
|  | if (qdq && qdq->d_fieldmask & QC_SPC_TIMER) | 
|  | mq->c[Q_SPC].timer	= qdq->d_spc_timer; | 
|  | if (qdq && qdq->d_fieldmask & QC_SPC_WARNS) | 
|  | mq->c[Q_SPC].warns	= qdq->d_spc_warns; | 
|  | if (qdq && qdq->d_fieldmask & QC_INO_TIMER) | 
|  | mq->c[Q_INO].timer	= qdq->d_ino_timer; | 
|  | if (qdq && qdq->d_fieldmask & QC_INO_WARNS) | 
|  | mq->c[Q_INO].warns	= qdq->d_ino_warns; | 
|  |  | 
|  | mutex_unlock(&q->lock); | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void bch2_fs_quota_exit(struct bch_fs *c) | 
|  | { | 
|  | unsigned i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(c->quotas); i++) | 
|  | genradix_free(&c->quotas[i].table); | 
|  | } | 
|  |  | 
|  | void bch2_fs_quota_init(struct bch_fs *c) | 
|  | { | 
|  | unsigned i; | 
|  |  | 
|  | for (i = 0; i < ARRAY_SIZE(c->quotas); i++) | 
|  | mutex_init(&c->quotas[i].lock); | 
|  | } | 
|  |  | 
|  | static struct bch_sb_field_quota *bch2_sb_get_or_create_quota(struct bch_sb_handle *sb) | 
|  | { | 
|  | struct bch_sb_field_quota *sb_quota = bch2_sb_field_get(sb->sb, quota); | 
|  |  | 
|  | if (sb_quota) | 
|  | return sb_quota; | 
|  |  | 
|  | sb_quota = bch2_sb_field_resize(sb, quota, sizeof(*sb_quota) / sizeof(u64)); | 
|  | if (sb_quota) { | 
|  | unsigned qtype, qc; | 
|  |  | 
|  | for (qtype = 0; qtype < QTYP_NR; qtype++) | 
|  | for (qc = 0; qc < Q_COUNTERS; qc++) | 
|  | sb_quota->q[qtype].c[qc].timelimit = | 
|  | cpu_to_le32(7 * 24 * 60 * 60); | 
|  | } | 
|  |  | 
|  | return sb_quota; | 
|  | } | 
|  |  | 
|  | static void bch2_sb_quota_read(struct bch_fs *c) | 
|  | { | 
|  | struct bch_sb_field_quota *sb_quota; | 
|  | unsigned i, j; | 
|  |  | 
|  | sb_quota = bch2_sb_field_get(c->disk_sb.sb, quota); | 
|  | if (!sb_quota) | 
|  | return; | 
|  |  | 
|  | for (i = 0; i < QTYP_NR; i++) { | 
|  | struct bch_memquota_type *q = &c->quotas[i]; | 
|  |  | 
|  | for (j = 0; j < Q_COUNTERS; j++) { | 
|  | q->limits[j].timelimit = | 
|  | le32_to_cpu(sb_quota->q[i].c[j].timelimit); | 
|  | q->limits[j].warnlimit = | 
|  | le32_to_cpu(sb_quota->q[i].c[j].warnlimit); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int bch2_fs_quota_read_inode(struct btree_trans *trans, | 
|  | struct btree_iter *iter, | 
|  | struct bkey_s_c k) | 
|  | { | 
|  | struct bch_fs *c = trans->c; | 
|  | struct bch_inode_unpacked u; | 
|  | struct bch_snapshot_tree s_t; | 
|  | u32 tree = bch2_snapshot_tree(c, k.k->p.snapshot); | 
|  |  | 
|  | int ret = bch2_snapshot_tree_lookup(trans, tree, &s_t); | 
|  | bch2_fs_inconsistent_on(bch2_err_matches(ret, ENOENT), c, | 
|  | "%s: snapshot tree %u not found", __func__, tree); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (!s_t.master_subvol) | 
|  | goto advance; | 
|  |  | 
|  | ret = bch2_inode_find_by_inum_nowarn_trans(trans, | 
|  | (subvol_inum) { | 
|  | le32_to_cpu(s_t.master_subvol), | 
|  | k.k->p.offset, | 
|  | }, &u); | 
|  | /* | 
|  | * Inode might be deleted in this snapshot - the easiest way to handle | 
|  | * that is to just skip it here: | 
|  | */ | 
|  | if (bch2_err_matches(ret, ENOENT)) | 
|  | goto advance; | 
|  |  | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | bch2_quota_acct(c, bch_qid(&u), Q_SPC, u.bi_sectors, | 
|  | KEY_TYPE_QUOTA_NOCHECK); | 
|  | bch2_quota_acct(c, bch_qid(&u), Q_INO, 1, | 
|  | KEY_TYPE_QUOTA_NOCHECK); | 
|  | advance: | 
|  | bch2_btree_iter_set_pos(iter, bpos_nosnap_successor(iter->pos)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int bch2_fs_quota_read(struct bch_fs *c) | 
|  | { | 
|  |  | 
|  | mutex_lock(&c->sb_lock); | 
|  | struct bch_sb_field_quota *sb_quota = bch2_sb_get_or_create_quota(&c->disk_sb); | 
|  | if (!sb_quota) { | 
|  | mutex_unlock(&c->sb_lock); | 
|  | return -BCH_ERR_ENOSPC_sb_quota; | 
|  | } | 
|  |  | 
|  | bch2_sb_quota_read(c); | 
|  | mutex_unlock(&c->sb_lock); | 
|  |  | 
|  | int ret = bch2_trans_run(c, | 
|  | for_each_btree_key(trans, iter, BTREE_ID_quotas, POS_MIN, | 
|  | BTREE_ITER_prefetch, k, | 
|  | __bch2_quota_set(c, k, NULL)) ?: | 
|  | for_each_btree_key(trans, iter, BTREE_ID_inodes, POS_MIN, | 
|  | BTREE_ITER_prefetch|BTREE_ITER_all_snapshots, k, | 
|  | bch2_fs_quota_read_inode(trans, &iter, k))); | 
|  | bch_err_fn(c, ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Enable/disable/delete quotas for an entire filesystem: */ | 
|  |  | 
|  | static int bch2_quota_enable(struct super_block	*sb, unsigned uflags) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  | struct bch_sb_field_quota *sb_quota; | 
|  | int ret = 0; | 
|  |  | 
|  | if (sb->s_flags & SB_RDONLY) | 
|  | return -EROFS; | 
|  |  | 
|  | /* Accounting must be enabled at mount time: */ | 
|  | if (uflags & (FS_QUOTA_UDQ_ACCT|FS_QUOTA_GDQ_ACCT|FS_QUOTA_PDQ_ACCT)) | 
|  | return -EINVAL; | 
|  |  | 
|  | /* Can't enable enforcement without accounting: */ | 
|  | if ((uflags & FS_QUOTA_UDQ_ENFD) && !c->opts.usrquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | if ((uflags & FS_QUOTA_GDQ_ENFD) && !c->opts.grpquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (uflags & FS_QUOTA_PDQ_ENFD && !c->opts.prjquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | mutex_lock(&c->sb_lock); | 
|  | sb_quota = bch2_sb_get_or_create_quota(&c->disk_sb); | 
|  | if (!sb_quota) { | 
|  | ret = -BCH_ERR_ENOSPC_sb_quota; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | if (uflags & FS_QUOTA_UDQ_ENFD) | 
|  | SET_BCH_SB_USRQUOTA(c->disk_sb.sb, true); | 
|  |  | 
|  | if (uflags & FS_QUOTA_GDQ_ENFD) | 
|  | SET_BCH_SB_GRPQUOTA(c->disk_sb.sb, true); | 
|  |  | 
|  | if (uflags & FS_QUOTA_PDQ_ENFD) | 
|  | SET_BCH_SB_PRJQUOTA(c->disk_sb.sb, true); | 
|  |  | 
|  | bch2_write_super(c); | 
|  | unlock: | 
|  | mutex_unlock(&c->sb_lock); | 
|  |  | 
|  | return bch2_err_class(ret); | 
|  | } | 
|  |  | 
|  | static int bch2_quota_disable(struct super_block *sb, unsigned uflags) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  |  | 
|  | if (sb->s_flags & SB_RDONLY) | 
|  | return -EROFS; | 
|  |  | 
|  | mutex_lock(&c->sb_lock); | 
|  | if (uflags & FS_QUOTA_UDQ_ENFD) | 
|  | SET_BCH_SB_USRQUOTA(c->disk_sb.sb, false); | 
|  |  | 
|  | if (uflags & FS_QUOTA_GDQ_ENFD) | 
|  | SET_BCH_SB_GRPQUOTA(c->disk_sb.sb, false); | 
|  |  | 
|  | if (uflags & FS_QUOTA_PDQ_ENFD) | 
|  | SET_BCH_SB_PRJQUOTA(c->disk_sb.sb, false); | 
|  |  | 
|  | bch2_write_super(c); | 
|  | mutex_unlock(&c->sb_lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int bch2_quota_remove(struct super_block *sb, unsigned uflags) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  | int ret; | 
|  |  | 
|  | if (sb->s_flags & SB_RDONLY) | 
|  | return -EROFS; | 
|  |  | 
|  | if (uflags & FS_USER_QUOTA) { | 
|  | if (c->opts.usrquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | ret = bch2_btree_delete_range(c, BTREE_ID_quotas, | 
|  | POS(QTYP_USR, 0), | 
|  | POS(QTYP_USR, U64_MAX), | 
|  | 0, NULL); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (uflags & FS_GROUP_QUOTA) { | 
|  | if (c->opts.grpquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | ret = bch2_btree_delete_range(c, BTREE_ID_quotas, | 
|  | POS(QTYP_GRP, 0), | 
|  | POS(QTYP_GRP, U64_MAX), | 
|  | 0, NULL); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (uflags & FS_PROJ_QUOTA) { | 
|  | if (c->opts.prjquota) | 
|  | return -EINVAL; | 
|  |  | 
|  | ret = bch2_btree_delete_range(c, BTREE_ID_quotas, | 
|  | POS(QTYP_PRJ, 0), | 
|  | POS(QTYP_PRJ, U64_MAX), | 
|  | 0, NULL); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Return quota status information, such as enforcements, quota file inode | 
|  | * numbers etc. | 
|  | */ | 
|  | static int bch2_quota_get_state(struct super_block *sb, struct qc_state *state) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  | unsigned qtypes = enabled_qtypes(c); | 
|  | unsigned i; | 
|  |  | 
|  | memset(state, 0, sizeof(*state)); | 
|  |  | 
|  | for (i = 0; i < QTYP_NR; i++) { | 
|  | state->s_state[i].flags |= QCI_SYSFILE; | 
|  |  | 
|  | if (!(qtypes & (1 << i))) | 
|  | continue; | 
|  |  | 
|  | state->s_state[i].flags |= QCI_ACCT_ENABLED; | 
|  |  | 
|  | state->s_state[i].spc_timelimit = c->quotas[i].limits[Q_SPC].timelimit; | 
|  | state->s_state[i].spc_warnlimit = c->quotas[i].limits[Q_SPC].warnlimit; | 
|  |  | 
|  | state->s_state[i].ino_timelimit = c->quotas[i].limits[Q_INO].timelimit; | 
|  | state->s_state[i].ino_warnlimit = c->quotas[i].limits[Q_INO].warnlimit; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Adjust quota timers & warnings | 
|  | */ | 
|  | static int bch2_quota_set_info(struct super_block *sb, int type, | 
|  | struct qc_info *info) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  | struct bch_sb_field_quota *sb_quota; | 
|  | int ret = 0; | 
|  |  | 
|  | if (0) { | 
|  | struct printbuf buf = PRINTBUF; | 
|  |  | 
|  | qc_info_to_text(&buf, info); | 
|  | pr_info("setting:\n%s", buf.buf); | 
|  | printbuf_exit(&buf); | 
|  | } | 
|  |  | 
|  | if (sb->s_flags & SB_RDONLY) | 
|  | return -EROFS; | 
|  |  | 
|  | if (type >= QTYP_NR) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (!((1 << type) & enabled_qtypes(c))) | 
|  | return -ESRCH; | 
|  |  | 
|  | if (info->i_fieldmask & | 
|  | ~(QC_SPC_TIMER|QC_INO_TIMER|QC_SPC_WARNS|QC_INO_WARNS)) | 
|  | return -EINVAL; | 
|  |  | 
|  | mutex_lock(&c->sb_lock); | 
|  | sb_quota = bch2_sb_get_or_create_quota(&c->disk_sb); | 
|  | if (!sb_quota) { | 
|  | ret = -BCH_ERR_ENOSPC_sb_quota; | 
|  | goto unlock; | 
|  | } | 
|  |  | 
|  | if (info->i_fieldmask & QC_SPC_TIMER) | 
|  | sb_quota->q[type].c[Q_SPC].timelimit = | 
|  | cpu_to_le32(info->i_spc_timelimit); | 
|  |  | 
|  | if (info->i_fieldmask & QC_SPC_WARNS) | 
|  | sb_quota->q[type].c[Q_SPC].warnlimit = | 
|  | cpu_to_le32(info->i_spc_warnlimit); | 
|  |  | 
|  | if (info->i_fieldmask & QC_INO_TIMER) | 
|  | sb_quota->q[type].c[Q_INO].timelimit = | 
|  | cpu_to_le32(info->i_ino_timelimit); | 
|  |  | 
|  | if (info->i_fieldmask & QC_INO_WARNS) | 
|  | sb_quota->q[type].c[Q_INO].warnlimit = | 
|  | cpu_to_le32(info->i_ino_warnlimit); | 
|  |  | 
|  | bch2_sb_quota_read(c); | 
|  |  | 
|  | bch2_write_super(c); | 
|  | unlock: | 
|  | mutex_unlock(&c->sb_lock); | 
|  |  | 
|  | return bch2_err_class(ret); | 
|  | } | 
|  |  | 
|  | /* Get/set individual quotas: */ | 
|  |  | 
|  | static void __bch2_quota_get(struct qc_dqblk *dst, struct bch_memquota *src) | 
|  | { | 
|  | dst->d_space		= src->c[Q_SPC].v << 9; | 
|  | dst->d_spc_hardlimit	= src->c[Q_SPC].hardlimit << 9; | 
|  | dst->d_spc_softlimit	= src->c[Q_SPC].softlimit << 9; | 
|  | dst->d_spc_timer	= src->c[Q_SPC].timer; | 
|  | dst->d_spc_warns	= src->c[Q_SPC].warns; | 
|  |  | 
|  | dst->d_ino_count	= src->c[Q_INO].v; | 
|  | dst->d_ino_hardlimit	= src->c[Q_INO].hardlimit; | 
|  | dst->d_ino_softlimit	= src->c[Q_INO].softlimit; | 
|  | dst->d_ino_timer	= src->c[Q_INO].timer; | 
|  | dst->d_ino_warns	= src->c[Q_INO].warns; | 
|  | } | 
|  |  | 
|  | static int bch2_get_quota(struct super_block *sb, struct kqid kqid, | 
|  | struct qc_dqblk *qdq) | 
|  | { | 
|  | struct bch_fs *c		= sb->s_fs_info; | 
|  | struct bch_memquota_type *q	= &c->quotas[kqid.type]; | 
|  | qid_t qid			= from_kqid(&init_user_ns, kqid); | 
|  | struct bch_memquota *mq; | 
|  |  | 
|  | memset(qdq, 0, sizeof(*qdq)); | 
|  |  | 
|  | mutex_lock(&q->lock); | 
|  | mq = genradix_ptr(&q->table, qid); | 
|  | if (mq) | 
|  | __bch2_quota_get(qdq, mq); | 
|  | mutex_unlock(&q->lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int bch2_get_next_quota(struct super_block *sb, struct kqid *kqid, | 
|  | struct qc_dqblk *qdq) | 
|  | { | 
|  | struct bch_fs *c		= sb->s_fs_info; | 
|  | struct bch_memquota_type *q	= &c->quotas[kqid->type]; | 
|  | qid_t qid			= from_kqid(&init_user_ns, *kqid); | 
|  | struct genradix_iter iter; | 
|  | struct bch_memquota *mq; | 
|  | int ret = 0; | 
|  |  | 
|  | mutex_lock(&q->lock); | 
|  |  | 
|  | genradix_for_each_from(&q->table, iter, mq, qid) | 
|  | if (memcmp(mq, page_address(ZERO_PAGE(0)), sizeof(*mq))) { | 
|  | __bch2_quota_get(qdq, mq); | 
|  | *kqid = make_kqid(current_user_ns(), kqid->type, iter.pos); | 
|  | goto found; | 
|  | } | 
|  |  | 
|  | ret = -ENOENT; | 
|  | found: | 
|  | mutex_unlock(&q->lock); | 
|  | return bch2_err_class(ret); | 
|  | } | 
|  |  | 
|  | static int bch2_set_quota_trans(struct btree_trans *trans, | 
|  | struct bkey_i_quota *new_quota, | 
|  | struct qc_dqblk *qdq) | 
|  | { | 
|  | struct btree_iter iter; | 
|  | struct bkey_s_c k; | 
|  | int ret; | 
|  |  | 
|  | k = bch2_bkey_get_iter(trans, &iter, BTREE_ID_quotas, new_quota->k.p, | 
|  | BTREE_ITER_slots|BTREE_ITER_intent); | 
|  | ret = bkey_err(k); | 
|  | if (unlikely(ret)) | 
|  | return ret; | 
|  |  | 
|  | if (k.k->type == KEY_TYPE_quota) | 
|  | new_quota->v = *bkey_s_c_to_quota(k).v; | 
|  |  | 
|  | if (qdq->d_fieldmask & QC_SPC_SOFT) | 
|  | new_quota->v.c[Q_SPC].softlimit = cpu_to_le64(qdq->d_spc_softlimit >> 9); | 
|  | if (qdq->d_fieldmask & QC_SPC_HARD) | 
|  | new_quota->v.c[Q_SPC].hardlimit = cpu_to_le64(qdq->d_spc_hardlimit >> 9); | 
|  |  | 
|  | if (qdq->d_fieldmask & QC_INO_SOFT) | 
|  | new_quota->v.c[Q_INO].softlimit = cpu_to_le64(qdq->d_ino_softlimit); | 
|  | if (qdq->d_fieldmask & QC_INO_HARD) | 
|  | new_quota->v.c[Q_INO].hardlimit = cpu_to_le64(qdq->d_ino_hardlimit); | 
|  |  | 
|  | ret = bch2_trans_update(trans, &iter, &new_quota->k_i, 0); | 
|  | bch2_trans_iter_exit(trans, &iter); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int bch2_set_quota(struct super_block *sb, struct kqid qid, | 
|  | struct qc_dqblk *qdq) | 
|  | { | 
|  | struct bch_fs *c = sb->s_fs_info; | 
|  | struct bkey_i_quota new_quota; | 
|  | int ret; | 
|  |  | 
|  | if (0) { | 
|  | struct printbuf buf = PRINTBUF; | 
|  |  | 
|  | qc_dqblk_to_text(&buf, qdq); | 
|  | pr_info("setting:\n%s", buf.buf); | 
|  | printbuf_exit(&buf); | 
|  | } | 
|  |  | 
|  | if (sb->s_flags & SB_RDONLY) | 
|  | return -EROFS; | 
|  |  | 
|  | bkey_quota_init(&new_quota.k_i); | 
|  | new_quota.k.p = POS(qid.type, from_kqid(&init_user_ns, qid)); | 
|  |  | 
|  | ret = bch2_trans_do(c, NULL, NULL, 0, | 
|  | bch2_set_quota_trans(trans, &new_quota, qdq)) ?: | 
|  | __bch2_quota_set(c, bkey_i_to_s_c(&new_quota.k_i), qdq); | 
|  |  | 
|  | return bch2_err_class(ret); | 
|  | } | 
|  |  | 
|  | const struct quotactl_ops bch2_quotactl_operations = { | 
|  | .quota_enable		= bch2_quota_enable, | 
|  | .quota_disable		= bch2_quota_disable, | 
|  | .rm_xquota		= bch2_quota_remove, | 
|  |  | 
|  | .get_state		= bch2_quota_get_state, | 
|  | .set_info		= bch2_quota_set_info, | 
|  |  | 
|  | .get_dqblk		= bch2_get_quota, | 
|  | .get_nextdqblk		= bch2_get_next_quota, | 
|  | .set_dqblk		= bch2_set_quota, | 
|  | }; | 
|  |  | 
|  | #endif /* CONFIG_BCACHEFS_QUOTA */ |