| // 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, |
| 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_invalid(struct bch_fs *c, struct bkey_s_c k, |
| enum bkey_invalid_flags flags, |
| struct printbuf *err) |
| { |
| int ret = 0; |
| |
| bkey_fsck_err_on(k.k->p.inode >= QTYP_NR, c, err, |
| 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_str(out, "i_fieldmask"); |
| prt_tab(out); |
| prt_printf(out, "%x", i->i_fieldmask); |
| prt_newline(out); |
| |
| prt_str(out, "i_flags"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_flags); |
| prt_newline(out); |
| |
| prt_str(out, "i_spc_timelimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_spc_timelimit); |
| prt_newline(out); |
| |
| prt_str(out, "i_ino_timelimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_ino_timelimit); |
| prt_newline(out); |
| |
| prt_str(out, "i_rt_spc_timelimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_rt_spc_timelimit); |
| prt_newline(out); |
| |
| prt_str(out, "i_spc_warnlimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_spc_warnlimit); |
| prt_newline(out); |
| |
| prt_str(out, "i_ino_warnlimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_ino_warnlimit); |
| prt_newline(out); |
| |
| prt_str(out, "i_rt_spc_warnlimit"); |
| prt_tab(out); |
| prt_printf(out, "%u", i->i_rt_spc_warnlimit); |
| prt_newline(out); |
| } |
| |
| static void qc_dqblk_to_text(struct printbuf *out, struct qc_dqblk *q) |
| { |
| printbuf_tabstops_reset(out); |
| printbuf_tabstop_push(out, 20); |
| |
| prt_str(out, "d_fieldmask"); |
| prt_tab(out); |
| prt_printf(out, "%x", q->d_fieldmask); |
| prt_newline(out); |
| |
| prt_str(out, "d_spc_hardlimit"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_spc_hardlimit); |
| prt_newline(out); |
| |
| prt_str(out, "d_spc_softlimit"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_spc_softlimit); |
| prt_newline(out); |
| |
| prt_str(out, "d_ino_hardlimit"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_ino_hardlimit); |
| prt_newline(out); |
| |
| prt_str(out, "d_ino_softlimit"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_ino_softlimit); |
| prt_newline(out); |
| |
| prt_str(out, "d_space"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_space); |
| prt_newline(out); |
| |
| prt_str(out, "d_ino_count"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_ino_count); |
| prt_newline(out); |
| |
| prt_str(out, "d_ino_timer"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_ino_timer); |
| prt_newline(out); |
| |
| prt_str(out, "d_spc_timer"); |
| prt_tab(out); |
| prt_printf(out, "%llu", q->d_spc_timer); |
| prt_newline(out); |
| |
| prt_str(out, "d_ino_warns"); |
| prt_tab(out); |
| prt_printf(out, "%i", q->d_ino_warns); |
| prt_newline(out); |
| |
| prt_str(out, "d_spc_warns"); |
| prt_tab(out); |
| prt_printf(out, "%i", q->d_spc_warns); |
| prt_newline(out); |
| } |
| |
| 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; |
| int ret; |
| |
| ret = bch2_snapshot_tree_lookup(trans, |
| bch2_snapshot_tree(c, k.k->p.snapshot), &s_t); |
| bch2_fs_inconsistent_on(bch2_err_matches(ret, ENOENT), c, |
| "%s: snapshot tree %u not found", __func__, |
| snapshot_t(c, k.k->p.snapshot)->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 */ |