1fb18a575SLuis Henriques // SPDX-License-Identifier: GPL-2.0 2fb18a575SLuis Henriques /* 3fb18a575SLuis Henriques * quota.c - CephFS quota 4fb18a575SLuis Henriques * 5fb18a575SLuis Henriques * Copyright (C) 2017-2018 SUSE 6fb18a575SLuis Henriques * 7fb18a575SLuis Henriques * This program is free software; you can redistribute it and/or 8fb18a575SLuis Henriques * modify it under the terms of the GNU General Public License 9fb18a575SLuis Henriques * as published by the Free Software Foundation; either version 2 10fb18a575SLuis Henriques * of the License, or (at your option) any later version. 11fb18a575SLuis Henriques * 12fb18a575SLuis Henriques * This program is distributed in the hope that it will be useful, 13fb18a575SLuis Henriques * but WITHOUT ANY WARRANTY; without even the implied warranty of 14fb18a575SLuis Henriques * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15fb18a575SLuis Henriques * GNU General Public License for more details. 16fb18a575SLuis Henriques * 17fb18a575SLuis Henriques * You should have received a copy of the GNU General Public License 18fb18a575SLuis Henriques * along with this program; if not, see <http://www.gnu.org/licenses/>. 19fb18a575SLuis Henriques */ 20fb18a575SLuis Henriques 219122eed5SLuis Henriques #include <linux/statfs.h> 229122eed5SLuis Henriques 23fb18a575SLuis Henriques #include "super.h" 24fb18a575SLuis Henriques #include "mds_client.h" 25fb18a575SLuis Henriques 26d557c48dSLuis Henriques void ceph_adjust_quota_realms_count(struct inode *inode, bool inc) 27cafe21a4SLuis Henriques { 28d557c48dSLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc; 29d557c48dSLuis Henriques if (inc) 30d557c48dSLuis Henriques atomic64_inc(&mdsc->quotarealms_count); 31d557c48dSLuis Henriques else 32d557c48dSLuis Henriques atomic64_dec(&mdsc->quotarealms_count); 33d557c48dSLuis Henriques } 34d557c48dSLuis Henriques 35d557c48dSLuis Henriques static inline bool ceph_has_realms_with_quotas(struct inode *inode) 36d557c48dSLuis Henriques { 37d557c48dSLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc; 38d557c48dSLuis Henriques return atomic64_read(&mdsc->quotarealms_count) > 0; 39cafe21a4SLuis Henriques } 40cafe21a4SLuis Henriques 41fb18a575SLuis Henriques void ceph_handle_quota(struct ceph_mds_client *mdsc, 42fb18a575SLuis Henriques struct ceph_mds_session *session, 43fb18a575SLuis Henriques struct ceph_msg *msg) 44fb18a575SLuis Henriques { 45fb18a575SLuis Henriques struct super_block *sb = mdsc->fsc->sb; 46fb18a575SLuis Henriques struct ceph_mds_quota *h = msg->front.iov_base; 47fb18a575SLuis Henriques struct ceph_vino vino; 48fb18a575SLuis Henriques struct inode *inode; 49fb18a575SLuis Henriques struct ceph_inode_info *ci; 50fb18a575SLuis Henriques 510fcf6c02SYan, Zheng if (msg->front.iov_len < sizeof(*h)) { 52fb18a575SLuis Henriques pr_err("%s corrupt message mds%d len %d\n", __func__, 53fb18a575SLuis Henriques session->s_mds, (int)msg->front.iov_len); 54fb18a575SLuis Henriques ceph_msg_dump(msg); 55fb18a575SLuis Henriques return; 56fb18a575SLuis Henriques } 57fb18a575SLuis Henriques 58fb18a575SLuis Henriques /* increment msg sequence number */ 59fb18a575SLuis Henriques mutex_lock(&session->s_mutex); 60fb18a575SLuis Henriques session->s_seq++; 61fb18a575SLuis Henriques mutex_unlock(&session->s_mutex); 62fb18a575SLuis Henriques 63fb18a575SLuis Henriques /* lookup inode */ 64fb18a575SLuis Henriques vino.ino = le64_to_cpu(h->ino); 65fb18a575SLuis Henriques vino.snap = CEPH_NOSNAP; 66fb18a575SLuis Henriques inode = ceph_find_inode(sb, vino); 67fb18a575SLuis Henriques if (!inode) { 68fb18a575SLuis Henriques pr_warn("Failed to find inode %llu\n", vino.ino); 69fb18a575SLuis Henriques return; 70fb18a575SLuis Henriques } 71fb18a575SLuis Henriques ci = ceph_inode(inode); 72fb18a575SLuis Henriques 73fb18a575SLuis Henriques spin_lock(&ci->i_ceph_lock); 74fb18a575SLuis Henriques ci->i_rbytes = le64_to_cpu(h->rbytes); 75fb18a575SLuis Henriques ci->i_rfiles = le64_to_cpu(h->rfiles); 76fb18a575SLuis Henriques ci->i_rsubdirs = le64_to_cpu(h->rsubdirs); 77d557c48dSLuis Henriques __ceph_update_quota(ci, le64_to_cpu(h->max_bytes), 78d557c48dSLuis Henriques le64_to_cpu(h->max_files)); 79fb18a575SLuis Henriques spin_unlock(&ci->i_ceph_lock); 80fb18a575SLuis Henriques 81fb18a575SLuis Henriques iput(inode); 82fb18a575SLuis Henriques } 83b7a29217SLuis Henriques 84cafe21a4SLuis Henriques /* 85cafe21a4SLuis Henriques * This function walks through the snaprealm for an inode and returns the 86cafe21a4SLuis Henriques * ceph_snap_realm for the first snaprealm that has quotas set (either max_files 87cafe21a4SLuis Henriques * or max_bytes). If the root is reached, return the root ceph_snap_realm 88cafe21a4SLuis Henriques * instead. 89cafe21a4SLuis Henriques * 90cafe21a4SLuis Henriques * Note that the caller is responsible for calling ceph_put_snap_realm() on the 91cafe21a4SLuis Henriques * returned realm. 92cafe21a4SLuis Henriques */ 93cafe21a4SLuis Henriques static struct ceph_snap_realm *get_quota_realm(struct ceph_mds_client *mdsc, 94cafe21a4SLuis Henriques struct inode *inode) 95cafe21a4SLuis Henriques { 96cafe21a4SLuis Henriques struct ceph_inode_info *ci = NULL; 97cafe21a4SLuis Henriques struct ceph_snap_realm *realm, *next; 98cafe21a4SLuis Henriques struct inode *in; 990eb6bbe4SYan, Zheng bool has_quota; 100cafe21a4SLuis Henriques 10125963669SYan, Zheng if (ceph_snap(inode) != CEPH_NOSNAP) 10225963669SYan, Zheng return NULL; 10325963669SYan, Zheng 104cafe21a4SLuis Henriques realm = ceph_inode(inode)->i_snap_realm; 10525963669SYan, Zheng if (realm) 106cafe21a4SLuis Henriques ceph_get_snap_realm(mdsc, realm); 10725963669SYan, Zheng else 10825963669SYan, Zheng pr_err_ratelimited("get_quota_realm: ino (%llx.%llx) " 10925963669SYan, Zheng "null i_snap_realm\n", ceph_vinop(inode)); 110cafe21a4SLuis Henriques while (realm) { 111e3161f17SLuis Henriques spin_lock(&realm->inodes_with_caps_lock); 112e3161f17SLuis Henriques in = realm->inode ? igrab(realm->inode) : NULL; 113e3161f17SLuis Henriques spin_unlock(&realm->inodes_with_caps_lock); 114e3161f17SLuis Henriques if (!in) 115cafe21a4SLuis Henriques break; 116e3161f17SLuis Henriques 117cafe21a4SLuis Henriques ci = ceph_inode(in); 118d557c48dSLuis Henriques has_quota = __ceph_has_any_quota(ci); 119cafe21a4SLuis Henriques iput(in); 1200eb6bbe4SYan, Zheng 121cafe21a4SLuis Henriques next = realm->parent; 1220eb6bbe4SYan, Zheng if (has_quota || !next) 1230eb6bbe4SYan, Zheng return realm; 1240eb6bbe4SYan, Zheng 125cafe21a4SLuis Henriques ceph_get_snap_realm(mdsc, next); 126cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, realm); 127cafe21a4SLuis Henriques realm = next; 128cafe21a4SLuis Henriques } 129cafe21a4SLuis Henriques if (realm) 130cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, realm); 131cafe21a4SLuis Henriques 132cafe21a4SLuis Henriques return NULL; 133cafe21a4SLuis Henriques } 134cafe21a4SLuis Henriques 135cafe21a4SLuis Henriques bool ceph_quota_is_same_realm(struct inode *old, struct inode *new) 136cafe21a4SLuis Henriques { 137cafe21a4SLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(old)->mdsc; 138cafe21a4SLuis Henriques struct ceph_snap_realm *old_realm, *new_realm; 139cafe21a4SLuis Henriques bool is_same; 140cafe21a4SLuis Henriques 141cafe21a4SLuis Henriques down_read(&mdsc->snap_rwsem); 142cafe21a4SLuis Henriques old_realm = get_quota_realm(mdsc, old); 143cafe21a4SLuis Henriques new_realm = get_quota_realm(mdsc, new); 144cafe21a4SLuis Henriques is_same = (old_realm == new_realm); 145cafe21a4SLuis Henriques up_read(&mdsc->snap_rwsem); 146cafe21a4SLuis Henriques 147cafe21a4SLuis Henriques if (old_realm) 148cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, old_realm); 149cafe21a4SLuis Henriques if (new_realm) 150cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, new_realm); 151cafe21a4SLuis Henriques 152cafe21a4SLuis Henriques return is_same; 153cafe21a4SLuis Henriques } 154cafe21a4SLuis Henriques 155b7a29217SLuis Henriques enum quota_check_op { 1562b83845fSLuis Henriques QUOTA_CHECK_MAX_FILES_OP, /* check quota max_files limit */ 1571ab302a0SLuis Henriques QUOTA_CHECK_MAX_BYTES_OP, /* check quota max_files limit */ 1581ab302a0SLuis Henriques QUOTA_CHECK_MAX_BYTES_APPROACHING_OP /* check if quota max_files 1591ab302a0SLuis Henriques limit is approaching */ 160b7a29217SLuis Henriques }; 161b7a29217SLuis Henriques 162b7a29217SLuis Henriques /* 163b7a29217SLuis Henriques * check_quota_exceeded() will walk up the snaprealm hierarchy and, for each 164b7a29217SLuis Henriques * realm, it will execute quota check operation defined by the 'op' parameter. 165b7a29217SLuis Henriques * The snaprealm walk is interrupted if the quota check detects that the quota 166b7a29217SLuis Henriques * is exceeded or if the root inode is reached. 167b7a29217SLuis Henriques */ 168b7a29217SLuis Henriques static bool check_quota_exceeded(struct inode *inode, enum quota_check_op op, 169b7a29217SLuis Henriques loff_t delta) 170b7a29217SLuis Henriques { 171b7a29217SLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc; 172b7a29217SLuis Henriques struct ceph_inode_info *ci; 173b7a29217SLuis Henriques struct ceph_snap_realm *realm, *next; 174b7a29217SLuis Henriques struct inode *in; 175b7a29217SLuis Henriques u64 max, rvalue; 176b7a29217SLuis Henriques bool exceeded = false; 177b7a29217SLuis Henriques 17825963669SYan, Zheng if (ceph_snap(inode) != CEPH_NOSNAP) 17925963669SYan, Zheng return false; 18025963669SYan, Zheng 181b7a29217SLuis Henriques down_read(&mdsc->snap_rwsem); 182b7a29217SLuis Henriques realm = ceph_inode(inode)->i_snap_realm; 18325963669SYan, Zheng if (realm) 184b7a29217SLuis Henriques ceph_get_snap_realm(mdsc, realm); 18525963669SYan, Zheng else 18625963669SYan, Zheng pr_err_ratelimited("check_quota_exceeded: ino (%llx.%llx) " 18725963669SYan, Zheng "null i_snap_realm\n", ceph_vinop(inode)); 188b7a29217SLuis Henriques while (realm) { 189e3161f17SLuis Henriques spin_lock(&realm->inodes_with_caps_lock); 190e3161f17SLuis Henriques in = realm->inode ? igrab(realm->inode) : NULL; 191e3161f17SLuis Henriques spin_unlock(&realm->inodes_with_caps_lock); 192e3161f17SLuis Henriques if (!in) 193b7a29217SLuis Henriques break; 194e3161f17SLuis Henriques 195b7a29217SLuis Henriques ci = ceph_inode(in); 196b7a29217SLuis Henriques spin_lock(&ci->i_ceph_lock); 197b7a29217SLuis Henriques if (op == QUOTA_CHECK_MAX_FILES_OP) { 198b7a29217SLuis Henriques max = ci->i_max_files; 199b7a29217SLuis Henriques rvalue = ci->i_rfiles + ci->i_rsubdirs; 2002b83845fSLuis Henriques } else { 2012b83845fSLuis Henriques max = ci->i_max_bytes; 2022b83845fSLuis Henriques rvalue = ci->i_rbytes; 203b7a29217SLuis Henriques } 204b7a29217SLuis Henriques spin_unlock(&ci->i_ceph_lock); 205b7a29217SLuis Henriques switch (op) { 206b7a29217SLuis Henriques case QUOTA_CHECK_MAX_FILES_OP: 207b7a29217SLuis Henriques exceeded = (max && (rvalue >= max)); 208b7a29217SLuis Henriques break; 2092b83845fSLuis Henriques case QUOTA_CHECK_MAX_BYTES_OP: 2102b83845fSLuis Henriques exceeded = (max && (rvalue + delta > max)); 2112b83845fSLuis Henriques break; 2121ab302a0SLuis Henriques case QUOTA_CHECK_MAX_BYTES_APPROACHING_OP: 2131ab302a0SLuis Henriques if (max) { 2141ab302a0SLuis Henriques if (rvalue >= max) 2151ab302a0SLuis Henriques exceeded = true; 2161ab302a0SLuis Henriques else { 2171ab302a0SLuis Henriques /* 2181ab302a0SLuis Henriques * when we're writing more that 1/16th 2191ab302a0SLuis Henriques * of the available space 2201ab302a0SLuis Henriques */ 2211ab302a0SLuis Henriques exceeded = 2221ab302a0SLuis Henriques (((max - rvalue) >> 4) < delta); 2231ab302a0SLuis Henriques } 2241ab302a0SLuis Henriques } 2251ab302a0SLuis Henriques break; 226b7a29217SLuis Henriques default: 227b7a29217SLuis Henriques /* Shouldn't happen */ 228b7a29217SLuis Henriques pr_warn("Invalid quota check op (%d)\n", op); 229b7a29217SLuis Henriques exceeded = true; /* Just break the loop */ 230b7a29217SLuis Henriques } 231b7a29217SLuis Henriques iput(in); 232b7a29217SLuis Henriques 233b7a29217SLuis Henriques next = realm->parent; 2340eb6bbe4SYan, Zheng if (exceeded || !next) 2350eb6bbe4SYan, Zheng break; 236b7a29217SLuis Henriques ceph_get_snap_realm(mdsc, next); 237b7a29217SLuis Henriques ceph_put_snap_realm(mdsc, realm); 238b7a29217SLuis Henriques realm = next; 239b7a29217SLuis Henriques } 240b7a29217SLuis Henriques ceph_put_snap_realm(mdsc, realm); 241b7a29217SLuis Henriques up_read(&mdsc->snap_rwsem); 242b7a29217SLuis Henriques 243b7a29217SLuis Henriques return exceeded; 244b7a29217SLuis Henriques } 245b7a29217SLuis Henriques 246b7a29217SLuis Henriques /* 247b7a29217SLuis Henriques * ceph_quota_is_max_files_exceeded - check if we can create a new file 248b7a29217SLuis Henriques * @inode: directory where a new file is being created 249b7a29217SLuis Henriques * 250b7a29217SLuis Henriques * This functions returns true is max_files quota allows a new file to be 251b7a29217SLuis Henriques * created. It is necessary to walk through the snaprealm hierarchy (until the 252b7a29217SLuis Henriques * FS root) to check all realms with quotas set. 253b7a29217SLuis Henriques */ 254b7a29217SLuis Henriques bool ceph_quota_is_max_files_exceeded(struct inode *inode) 255b7a29217SLuis Henriques { 256d557c48dSLuis Henriques if (!ceph_has_realms_with_quotas(inode)) 257d557c48dSLuis Henriques return false; 258d557c48dSLuis Henriques 259b7a29217SLuis Henriques WARN_ON(!S_ISDIR(inode->i_mode)); 260b7a29217SLuis Henriques 261b7a29217SLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_FILES_OP, 0); 262b7a29217SLuis Henriques } 2632b83845fSLuis Henriques 2642b83845fSLuis Henriques /* 2652b83845fSLuis Henriques * ceph_quota_is_max_bytes_exceeded - check if we can write to a file 2662b83845fSLuis Henriques * @inode: inode being written 2672b83845fSLuis Henriques * @newsize: new size if write succeeds 2682b83845fSLuis Henriques * 2692b83845fSLuis Henriques * This functions returns true is max_bytes quota allows a file size to reach 2702b83845fSLuis Henriques * @newsize; it returns false otherwise. 2712b83845fSLuis Henriques */ 2722b83845fSLuis Henriques bool ceph_quota_is_max_bytes_exceeded(struct inode *inode, loff_t newsize) 2732b83845fSLuis Henriques { 2742b83845fSLuis Henriques loff_t size = i_size_read(inode); 2752b83845fSLuis Henriques 276d557c48dSLuis Henriques if (!ceph_has_realms_with_quotas(inode)) 277d557c48dSLuis Henriques return false; 278d557c48dSLuis Henriques 2792b83845fSLuis Henriques /* return immediately if we're decreasing file size */ 2802b83845fSLuis Henriques if (newsize <= size) 2812b83845fSLuis Henriques return false; 2822b83845fSLuis Henriques 2832b83845fSLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_OP, (newsize - size)); 2842b83845fSLuis Henriques } 2851ab302a0SLuis Henriques 2861ab302a0SLuis Henriques /* 2871ab302a0SLuis Henriques * ceph_quota_is_max_bytes_approaching - check if we're reaching max_bytes 2881ab302a0SLuis Henriques * @inode: inode being written 2891ab302a0SLuis Henriques * @newsize: new size if write succeeds 2901ab302a0SLuis Henriques * 2911ab302a0SLuis Henriques * This function returns true if the new file size @newsize will be consuming 2921ab302a0SLuis Henriques * more than 1/16th of the available quota space; it returns false otherwise. 2931ab302a0SLuis Henriques */ 2941ab302a0SLuis Henriques bool ceph_quota_is_max_bytes_approaching(struct inode *inode, loff_t newsize) 2951ab302a0SLuis Henriques { 2961ab302a0SLuis Henriques loff_t size = ceph_inode(inode)->i_reported_size; 2971ab302a0SLuis Henriques 298d557c48dSLuis Henriques if (!ceph_has_realms_with_quotas(inode)) 299d557c48dSLuis Henriques return false; 300d557c48dSLuis Henriques 3011ab302a0SLuis Henriques /* return immediately if we're decreasing file size */ 3021ab302a0SLuis Henriques if (newsize <= size) 3031ab302a0SLuis Henriques return false; 3041ab302a0SLuis Henriques 3051ab302a0SLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_APPROACHING_OP, 3061ab302a0SLuis Henriques (newsize - size)); 3071ab302a0SLuis Henriques } 3089122eed5SLuis Henriques 3099122eed5SLuis Henriques /* 3109122eed5SLuis Henriques * ceph_quota_update_statfs - if root has quota update statfs with quota status 3119122eed5SLuis Henriques * @fsc: filesystem client instance 3129122eed5SLuis Henriques * @buf: statfs to update 3139122eed5SLuis Henriques * 3149122eed5SLuis Henriques * If the mounted filesystem root has max_bytes quota set, update the filesystem 3159122eed5SLuis Henriques * statistics with the quota status. 3169122eed5SLuis Henriques * 3179122eed5SLuis Henriques * This function returns true if the stats have been updated, false otherwise. 3189122eed5SLuis Henriques */ 3199122eed5SLuis Henriques bool ceph_quota_update_statfs(struct ceph_fs_client *fsc, struct kstatfs *buf) 3209122eed5SLuis Henriques { 3219122eed5SLuis Henriques struct ceph_mds_client *mdsc = fsc->mdsc; 3229122eed5SLuis Henriques struct ceph_inode_info *ci; 3239122eed5SLuis Henriques struct ceph_snap_realm *realm; 3249122eed5SLuis Henriques struct inode *in; 3259122eed5SLuis Henriques u64 total = 0, used, free; 3269122eed5SLuis Henriques bool is_updated = false; 3279122eed5SLuis Henriques 3289122eed5SLuis Henriques down_read(&mdsc->snap_rwsem); 3299122eed5SLuis Henriques realm = get_quota_realm(mdsc, d_inode(fsc->sb->s_root)); 3309122eed5SLuis Henriques up_read(&mdsc->snap_rwsem); 3319122eed5SLuis Henriques if (!realm) 3329122eed5SLuis Henriques return false; 3339122eed5SLuis Henriques 3349122eed5SLuis Henriques spin_lock(&realm->inodes_with_caps_lock); 3359122eed5SLuis Henriques in = realm->inode ? igrab(realm->inode) : NULL; 3369122eed5SLuis Henriques spin_unlock(&realm->inodes_with_caps_lock); 3379122eed5SLuis Henriques if (in) { 3389122eed5SLuis Henriques ci = ceph_inode(in); 3399122eed5SLuis Henriques spin_lock(&ci->i_ceph_lock); 3409122eed5SLuis Henriques if (ci->i_max_bytes) { 3419122eed5SLuis Henriques total = ci->i_max_bytes >> CEPH_BLOCK_SHIFT; 3429122eed5SLuis Henriques used = ci->i_rbytes >> CEPH_BLOCK_SHIFT; 3439122eed5SLuis Henriques /* It is possible for a quota to be exceeded. 3449122eed5SLuis Henriques * Report 'zero' in that case 3459122eed5SLuis Henriques */ 3469122eed5SLuis Henriques free = total > used ? total - used : 0; 3479122eed5SLuis Henriques } 3489122eed5SLuis Henriques spin_unlock(&ci->i_ceph_lock); 3499122eed5SLuis Henriques if (total) { 3509122eed5SLuis Henriques buf->f_blocks = total; 3519122eed5SLuis Henriques buf->f_bfree = free; 3529122eed5SLuis Henriques buf->f_bavail = free; 3539122eed5SLuis Henriques is_updated = true; 3549122eed5SLuis Henriques } 3559122eed5SLuis Henriques iput(in); 3569122eed5SLuis Henriques } 3579122eed5SLuis Henriques ceph_put_snap_realm(mdsc, realm); 3589122eed5SLuis Henriques 3599122eed5SLuis Henriques return is_updated; 3609122eed5SLuis Henriques } 3619122eed5SLuis Henriques 362