xref: /openbmc/linux/fs/ceph/quota.c (revision 0eb6bbe4)
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