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 21fb18a575SLuis Henriques #include "super.h" 22fb18a575SLuis Henriques #include "mds_client.h" 23fb18a575SLuis Henriques 24cafe21a4SLuis Henriques static inline bool ceph_has_quota(struct ceph_inode_info *ci) 25cafe21a4SLuis Henriques { 26cafe21a4SLuis Henriques return (ci && (ci->i_max_files || ci->i_max_bytes)); 27cafe21a4SLuis Henriques } 28cafe21a4SLuis Henriques 29fb18a575SLuis Henriques void ceph_handle_quota(struct ceph_mds_client *mdsc, 30fb18a575SLuis Henriques struct ceph_mds_session *session, 31fb18a575SLuis Henriques struct ceph_msg *msg) 32fb18a575SLuis Henriques { 33fb18a575SLuis Henriques struct super_block *sb = mdsc->fsc->sb; 34fb18a575SLuis Henriques struct ceph_mds_quota *h = msg->front.iov_base; 35fb18a575SLuis Henriques struct ceph_vino vino; 36fb18a575SLuis Henriques struct inode *inode; 37fb18a575SLuis Henriques struct ceph_inode_info *ci; 38fb18a575SLuis Henriques 39fb18a575SLuis Henriques if (msg->front.iov_len != sizeof(*h)) { 40fb18a575SLuis Henriques pr_err("%s corrupt message mds%d len %d\n", __func__, 41fb18a575SLuis Henriques session->s_mds, (int)msg->front.iov_len); 42fb18a575SLuis Henriques ceph_msg_dump(msg); 43fb18a575SLuis Henriques return; 44fb18a575SLuis Henriques } 45fb18a575SLuis Henriques 46fb18a575SLuis Henriques /* increment msg sequence number */ 47fb18a575SLuis Henriques mutex_lock(&session->s_mutex); 48fb18a575SLuis Henriques session->s_seq++; 49fb18a575SLuis Henriques mutex_unlock(&session->s_mutex); 50fb18a575SLuis Henriques 51fb18a575SLuis Henriques /* lookup inode */ 52fb18a575SLuis Henriques vino.ino = le64_to_cpu(h->ino); 53fb18a575SLuis Henriques vino.snap = CEPH_NOSNAP; 54fb18a575SLuis Henriques inode = ceph_find_inode(sb, vino); 55fb18a575SLuis Henriques if (!inode) { 56fb18a575SLuis Henriques pr_warn("Failed to find inode %llu\n", vino.ino); 57fb18a575SLuis Henriques return; 58fb18a575SLuis Henriques } 59fb18a575SLuis Henriques ci = ceph_inode(inode); 60fb18a575SLuis Henriques 61fb18a575SLuis Henriques spin_lock(&ci->i_ceph_lock); 62fb18a575SLuis Henriques ci->i_rbytes = le64_to_cpu(h->rbytes); 63fb18a575SLuis Henriques ci->i_rfiles = le64_to_cpu(h->rfiles); 64fb18a575SLuis Henriques ci->i_rsubdirs = le64_to_cpu(h->rsubdirs); 65fb18a575SLuis Henriques ci->i_max_bytes = le64_to_cpu(h->max_bytes); 66fb18a575SLuis Henriques ci->i_max_files = le64_to_cpu(h->max_files); 67fb18a575SLuis Henriques spin_unlock(&ci->i_ceph_lock); 68fb18a575SLuis Henriques 69fb18a575SLuis Henriques iput(inode); 70fb18a575SLuis Henriques } 71b7a29217SLuis Henriques 72cafe21a4SLuis Henriques /* 73cafe21a4SLuis Henriques * This function walks through the snaprealm for an inode and returns the 74cafe21a4SLuis Henriques * ceph_snap_realm for the first snaprealm that has quotas set (either max_files 75cafe21a4SLuis Henriques * or max_bytes). If the root is reached, return the root ceph_snap_realm 76cafe21a4SLuis Henriques * instead. 77cafe21a4SLuis Henriques * 78cafe21a4SLuis Henriques * Note that the caller is responsible for calling ceph_put_snap_realm() on the 79cafe21a4SLuis Henriques * returned realm. 80cafe21a4SLuis Henriques */ 81cafe21a4SLuis Henriques static struct ceph_snap_realm *get_quota_realm(struct ceph_mds_client *mdsc, 82cafe21a4SLuis Henriques struct inode *inode) 83cafe21a4SLuis Henriques { 84cafe21a4SLuis Henriques struct ceph_inode_info *ci = NULL; 85cafe21a4SLuis Henriques struct ceph_snap_realm *realm, *next; 86cafe21a4SLuis Henriques struct ceph_vino vino; 87cafe21a4SLuis Henriques struct inode *in; 880eb6bbe4SYan, Zheng bool has_quota; 89cafe21a4SLuis Henriques 9025963669SYan, Zheng if (ceph_snap(inode) != CEPH_NOSNAP) 9125963669SYan, Zheng return NULL; 9225963669SYan, Zheng 93cafe21a4SLuis Henriques realm = ceph_inode(inode)->i_snap_realm; 9425963669SYan, Zheng if (realm) 95cafe21a4SLuis Henriques ceph_get_snap_realm(mdsc, realm); 9625963669SYan, Zheng else 9725963669SYan, Zheng pr_err_ratelimited("get_quota_realm: ino (%llx.%llx) " 9825963669SYan, Zheng "null i_snap_realm\n", ceph_vinop(inode)); 99cafe21a4SLuis Henriques while (realm) { 100cafe21a4SLuis Henriques vino.ino = realm->ino; 101cafe21a4SLuis Henriques vino.snap = CEPH_NOSNAP; 102cafe21a4SLuis Henriques in = ceph_find_inode(inode->i_sb, vino); 103cafe21a4SLuis Henriques if (!in) { 104cafe21a4SLuis Henriques pr_warn("Failed to find inode for %llu\n", vino.ino); 105cafe21a4SLuis Henriques break; 106cafe21a4SLuis Henriques } 107cafe21a4SLuis Henriques ci = ceph_inode(in); 1080eb6bbe4SYan, Zheng has_quota = ceph_has_quota(ci); 109cafe21a4SLuis Henriques iput(in); 1100eb6bbe4SYan, Zheng 111cafe21a4SLuis Henriques next = realm->parent; 1120eb6bbe4SYan, Zheng if (has_quota || !next) 1130eb6bbe4SYan, Zheng return realm; 1140eb6bbe4SYan, Zheng 115cafe21a4SLuis Henriques ceph_get_snap_realm(mdsc, next); 116cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, realm); 117cafe21a4SLuis Henriques realm = next; 118cafe21a4SLuis Henriques } 119cafe21a4SLuis Henriques if (realm) 120cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, realm); 121cafe21a4SLuis Henriques 122cafe21a4SLuis Henriques return NULL; 123cafe21a4SLuis Henriques } 124cafe21a4SLuis Henriques 125cafe21a4SLuis Henriques bool ceph_quota_is_same_realm(struct inode *old, struct inode *new) 126cafe21a4SLuis Henriques { 127cafe21a4SLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(old)->mdsc; 128cafe21a4SLuis Henriques struct ceph_snap_realm *old_realm, *new_realm; 129cafe21a4SLuis Henriques bool is_same; 130cafe21a4SLuis Henriques 131cafe21a4SLuis Henriques down_read(&mdsc->snap_rwsem); 132cafe21a4SLuis Henriques old_realm = get_quota_realm(mdsc, old); 133cafe21a4SLuis Henriques new_realm = get_quota_realm(mdsc, new); 134cafe21a4SLuis Henriques is_same = (old_realm == new_realm); 135cafe21a4SLuis Henriques up_read(&mdsc->snap_rwsem); 136cafe21a4SLuis Henriques 137cafe21a4SLuis Henriques if (old_realm) 138cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, old_realm); 139cafe21a4SLuis Henriques if (new_realm) 140cafe21a4SLuis Henriques ceph_put_snap_realm(mdsc, new_realm); 141cafe21a4SLuis Henriques 142cafe21a4SLuis Henriques return is_same; 143cafe21a4SLuis Henriques } 144cafe21a4SLuis Henriques 145b7a29217SLuis Henriques enum quota_check_op { 1462b83845fSLuis Henriques QUOTA_CHECK_MAX_FILES_OP, /* check quota max_files limit */ 1471ab302a0SLuis Henriques QUOTA_CHECK_MAX_BYTES_OP, /* check quota max_files limit */ 1481ab302a0SLuis Henriques QUOTA_CHECK_MAX_BYTES_APPROACHING_OP /* check if quota max_files 1491ab302a0SLuis Henriques limit is approaching */ 150b7a29217SLuis Henriques }; 151b7a29217SLuis Henriques 152b7a29217SLuis Henriques /* 153b7a29217SLuis Henriques * check_quota_exceeded() will walk up the snaprealm hierarchy and, for each 154b7a29217SLuis Henriques * realm, it will execute quota check operation defined by the 'op' parameter. 155b7a29217SLuis Henriques * The snaprealm walk is interrupted if the quota check detects that the quota 156b7a29217SLuis Henriques * is exceeded or if the root inode is reached. 157b7a29217SLuis Henriques */ 158b7a29217SLuis Henriques static bool check_quota_exceeded(struct inode *inode, enum quota_check_op op, 159b7a29217SLuis Henriques loff_t delta) 160b7a29217SLuis Henriques { 161b7a29217SLuis Henriques struct ceph_mds_client *mdsc = ceph_inode_to_client(inode)->mdsc; 162b7a29217SLuis Henriques struct ceph_inode_info *ci; 163b7a29217SLuis Henriques struct ceph_snap_realm *realm, *next; 164b7a29217SLuis Henriques struct ceph_vino vino; 165b7a29217SLuis Henriques struct inode *in; 166b7a29217SLuis Henriques u64 max, rvalue; 167b7a29217SLuis Henriques bool exceeded = false; 168b7a29217SLuis Henriques 16925963669SYan, Zheng if (ceph_snap(inode) != CEPH_NOSNAP) 17025963669SYan, Zheng return false; 17125963669SYan, Zheng 172b7a29217SLuis Henriques down_read(&mdsc->snap_rwsem); 173b7a29217SLuis Henriques realm = ceph_inode(inode)->i_snap_realm; 17425963669SYan, Zheng if (realm) 175b7a29217SLuis Henriques ceph_get_snap_realm(mdsc, realm); 17625963669SYan, Zheng else 17725963669SYan, Zheng pr_err_ratelimited("check_quota_exceeded: ino (%llx.%llx) " 17825963669SYan, Zheng "null i_snap_realm\n", ceph_vinop(inode)); 179b7a29217SLuis Henriques while (realm) { 180b7a29217SLuis Henriques vino.ino = realm->ino; 181b7a29217SLuis Henriques vino.snap = CEPH_NOSNAP; 182b7a29217SLuis Henriques in = ceph_find_inode(inode->i_sb, vino); 183b7a29217SLuis Henriques if (!in) { 184b7a29217SLuis Henriques pr_warn("Failed to find inode for %llu\n", vino.ino); 185b7a29217SLuis Henriques break; 186b7a29217SLuis Henriques } 187b7a29217SLuis Henriques ci = ceph_inode(in); 188b7a29217SLuis Henriques spin_lock(&ci->i_ceph_lock); 189b7a29217SLuis Henriques if (op == QUOTA_CHECK_MAX_FILES_OP) { 190b7a29217SLuis Henriques max = ci->i_max_files; 191b7a29217SLuis Henriques rvalue = ci->i_rfiles + ci->i_rsubdirs; 1922b83845fSLuis Henriques } else { 1932b83845fSLuis Henriques max = ci->i_max_bytes; 1942b83845fSLuis Henriques rvalue = ci->i_rbytes; 195b7a29217SLuis Henriques } 196b7a29217SLuis Henriques spin_unlock(&ci->i_ceph_lock); 197b7a29217SLuis Henriques switch (op) { 198b7a29217SLuis Henriques case QUOTA_CHECK_MAX_FILES_OP: 199b7a29217SLuis Henriques exceeded = (max && (rvalue >= max)); 200b7a29217SLuis Henriques break; 2012b83845fSLuis Henriques case QUOTA_CHECK_MAX_BYTES_OP: 2022b83845fSLuis Henriques exceeded = (max && (rvalue + delta > max)); 2032b83845fSLuis Henriques break; 2041ab302a0SLuis Henriques case QUOTA_CHECK_MAX_BYTES_APPROACHING_OP: 2051ab302a0SLuis Henriques if (max) { 2061ab302a0SLuis Henriques if (rvalue >= max) 2071ab302a0SLuis Henriques exceeded = true; 2081ab302a0SLuis Henriques else { 2091ab302a0SLuis Henriques /* 2101ab302a0SLuis Henriques * when we're writing more that 1/16th 2111ab302a0SLuis Henriques * of the available space 2121ab302a0SLuis Henriques */ 2131ab302a0SLuis Henriques exceeded = 2141ab302a0SLuis Henriques (((max - rvalue) >> 4) < delta); 2151ab302a0SLuis Henriques } 2161ab302a0SLuis Henriques } 2171ab302a0SLuis Henriques break; 218b7a29217SLuis Henriques default: 219b7a29217SLuis Henriques /* Shouldn't happen */ 220b7a29217SLuis Henriques pr_warn("Invalid quota check op (%d)\n", op); 221b7a29217SLuis Henriques exceeded = true; /* Just break the loop */ 222b7a29217SLuis Henriques } 223b7a29217SLuis Henriques iput(in); 224b7a29217SLuis Henriques 225b7a29217SLuis Henriques next = realm->parent; 2260eb6bbe4SYan, Zheng if (exceeded || !next) 2270eb6bbe4SYan, Zheng break; 228b7a29217SLuis Henriques ceph_get_snap_realm(mdsc, next); 229b7a29217SLuis Henriques ceph_put_snap_realm(mdsc, realm); 230b7a29217SLuis Henriques realm = next; 231b7a29217SLuis Henriques } 232b7a29217SLuis Henriques ceph_put_snap_realm(mdsc, realm); 233b7a29217SLuis Henriques up_read(&mdsc->snap_rwsem); 234b7a29217SLuis Henriques 235b7a29217SLuis Henriques return exceeded; 236b7a29217SLuis Henriques } 237b7a29217SLuis Henriques 238b7a29217SLuis Henriques /* 239b7a29217SLuis Henriques * ceph_quota_is_max_files_exceeded - check if we can create a new file 240b7a29217SLuis Henriques * @inode: directory where a new file is being created 241b7a29217SLuis Henriques * 242b7a29217SLuis Henriques * This functions returns true is max_files quota allows a new file to be 243b7a29217SLuis Henriques * created. It is necessary to walk through the snaprealm hierarchy (until the 244b7a29217SLuis Henriques * FS root) to check all realms with quotas set. 245b7a29217SLuis Henriques */ 246b7a29217SLuis Henriques bool ceph_quota_is_max_files_exceeded(struct inode *inode) 247b7a29217SLuis Henriques { 248b7a29217SLuis Henriques WARN_ON(!S_ISDIR(inode->i_mode)); 249b7a29217SLuis Henriques 250b7a29217SLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_FILES_OP, 0); 251b7a29217SLuis Henriques } 2522b83845fSLuis Henriques 2532b83845fSLuis Henriques /* 2542b83845fSLuis Henriques * ceph_quota_is_max_bytes_exceeded - check if we can write to a file 2552b83845fSLuis Henriques * @inode: inode being written 2562b83845fSLuis Henriques * @newsize: new size if write succeeds 2572b83845fSLuis Henriques * 2582b83845fSLuis Henriques * This functions returns true is max_bytes quota allows a file size to reach 2592b83845fSLuis Henriques * @newsize; it returns false otherwise. 2602b83845fSLuis Henriques */ 2612b83845fSLuis Henriques bool ceph_quota_is_max_bytes_exceeded(struct inode *inode, loff_t newsize) 2622b83845fSLuis Henriques { 2632b83845fSLuis Henriques loff_t size = i_size_read(inode); 2642b83845fSLuis Henriques 2652b83845fSLuis Henriques /* return immediately if we're decreasing file size */ 2662b83845fSLuis Henriques if (newsize <= size) 2672b83845fSLuis Henriques return false; 2682b83845fSLuis Henriques 2692b83845fSLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_OP, (newsize - size)); 2702b83845fSLuis Henriques } 2711ab302a0SLuis Henriques 2721ab302a0SLuis Henriques /* 2731ab302a0SLuis Henriques * ceph_quota_is_max_bytes_approaching - check if we're reaching max_bytes 2741ab302a0SLuis Henriques * @inode: inode being written 2751ab302a0SLuis Henriques * @newsize: new size if write succeeds 2761ab302a0SLuis Henriques * 2771ab302a0SLuis Henriques * This function returns true if the new file size @newsize will be consuming 2781ab302a0SLuis Henriques * more than 1/16th of the available quota space; it returns false otherwise. 2791ab302a0SLuis Henriques */ 2801ab302a0SLuis Henriques bool ceph_quota_is_max_bytes_approaching(struct inode *inode, loff_t newsize) 2811ab302a0SLuis Henriques { 2821ab302a0SLuis Henriques loff_t size = ceph_inode(inode)->i_reported_size; 2831ab302a0SLuis Henriques 2841ab302a0SLuis Henriques /* return immediately if we're decreasing file size */ 2851ab302a0SLuis Henriques if (newsize <= size) 2861ab302a0SLuis Henriques return false; 2871ab302a0SLuis Henriques 2881ab302a0SLuis Henriques return check_quota_exceeded(inode, QUOTA_CHECK_MAX_BYTES_APPROACHING_OP, 2891ab302a0SLuis Henriques (newsize - size)); 2901ab302a0SLuis Henriques } 291