xref: /linux/fs/afs/vl_alias.c (revision bfb921b2a9d5d1123d1d10b196a39db629ddef87)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* AFS cell alias detection
3  *
4  * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
5  * Written by David Howells (dhowells@redhat.com)
6  */
7 
8 #include <linux/slab.h>
9 #include <linux/sched.h>
10 #include <linux/namei.h>
11 #include <keys/rxrpc-type.h>
12 #include "internal.h"
13 
14 /*
15  * Sample a volume.
16  */
17 static struct afs_volume *afs_sample_volume(struct afs_cell *cell, struct key *key,
18 					    const char *name, unsigned int namelen)
19 {
20 	struct afs_volume *volume;
21 	struct afs_fs_context fc = {
22 		.type		= 0, /* Explicitly leave it to the VLDB */
23 		.volnamesz	= namelen,
24 		.volname	= name,
25 		.net		= cell->net,
26 		.cell		= cell,
27 		.key		= key, /* This might need to be something */
28 	};
29 
30 	volume = afs_create_volume(&fc);
31 	_leave(" = %p", volume);
32 	return volume;
33 }
34 
35 /*
36  * Compare the address lists of a pair of fileservers.
37  */
38 static int afs_compare_fs_alists(const struct afs_server *server_a,
39 				 const struct afs_server *server_b)
40 {
41 	const struct afs_addr_list *la, *lb;
42 	int a = 0, b = 0, addr_matches = 0;
43 
44 	la = rcu_dereference(server_a->endpoint_state)->addresses;
45 	lb = rcu_dereference(server_b->endpoint_state)->addresses;
46 
47 	while (a < la->nr_addrs && b < lb->nr_addrs) {
48 		unsigned long pa = (unsigned long)la->addrs[a].peer;
49 		unsigned long pb = (unsigned long)lb->addrs[b].peer;
50 		long diff = pa - pb;
51 
52 		if (diff < 0) {
53 			a++;
54 		} else if (diff > 0) {
55 			b++;
56 		} else {
57 			addr_matches++;
58 			a++;
59 			b++;
60 		}
61 	}
62 
63 	return addr_matches;
64 }
65 
66 /*
67  * Compare the fileserver lists of two volumes.  The server lists are sorted in
68  * order of ascending UUID.
69  */
70 static int afs_compare_volume_slists(const struct afs_volume *vol_a,
71 				     const struct afs_volume *vol_b)
72 {
73 	const struct afs_server_list *la, *lb;
74 	int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
75 
76 	la = rcu_dereference(vol_a->servers);
77 	lb = rcu_dereference(vol_b->servers);
78 
79 	for (i = 0; i < AFS_MAXTYPES; i++)
80 		if (vol_a->vids[i] != vol_b->vids[i])
81 			return 0;
82 
83 	while (a < la->nr_servers && b < lb->nr_servers) {
84 		const struct afs_server *server_a = la->servers[a].server;
85 		const struct afs_server *server_b = lb->servers[b].server;
86 		int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
87 
88 		if (diff < 0) {
89 			a++;
90 		} else if (diff > 0) {
91 			b++;
92 		} else {
93 			uuid_matches++;
94 			addr_matches += afs_compare_fs_alists(server_a, server_b);
95 			a++;
96 			b++;
97 		}
98 	}
99 
100 	_leave(" = %d [um %d]", addr_matches, uuid_matches);
101 	return addr_matches;
102 }
103 
104 /*
105  * Compare root.cell volumes.
106  */
107 static int afs_compare_cell_roots(struct afs_cell *cell)
108 {
109 	struct afs_cell *p;
110 
111 	_enter("");
112 
113 	rcu_read_lock();
114 
115 	hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
116 		if (p == cell || p->alias_of)
117 			continue;
118 		if (!p->root_volume)
119 			continue; /* Ignore cells that don't have a root.cell volume. */
120 
121 		if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
122 			goto is_alias;
123 	}
124 
125 	rcu_read_unlock();
126 	_leave(" = 0");
127 	return 0;
128 
129 is_alias:
130 	rcu_read_unlock();
131 	cell->alias_of = afs_use_cell(p, afs_cell_trace_use_alias);
132 	return 1;
133 }
134 
135 /*
136  * Query the new cell for a volume from a cell we're already using.
137  */
138 static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
139 				   struct afs_cell *p)
140 {
141 	struct afs_volume *volume, *pvol = NULL;
142 	int ret;
143 
144 	/* Arbitrarily pick a volume from the list. */
145 	read_seqlock_excl(&p->volume_lock);
146 	if (!RB_EMPTY_ROOT(&p->volumes))
147 		pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
148 					       struct afs_volume, cell_node),
149 				      afs_volume_trace_get_query_alias);
150 	read_sequnlock_excl(&p->volume_lock);
151 	if (!pvol)
152 		return 0;
153 
154 	_enter("%s:%s", cell->name, pvol->name);
155 
156 	/* And see if it's in the new cell. */
157 	volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
158 	if (IS_ERR(volume)) {
159 		afs_put_volume(pvol, afs_volume_trace_put_query_alias);
160 		if (PTR_ERR(volume) != -ENOMEDIUM)
161 			return PTR_ERR(volume);
162 		/* That volume is not in the new cell, so not an alias */
163 		return 0;
164 	}
165 
166 	/* The new cell has a like-named volume also - compare volume ID,
167 	 * server and address lists.
168 	 */
169 	ret = 0;
170 	if (pvol->vid == volume->vid) {
171 		rcu_read_lock();
172 		if (afs_compare_volume_slists(volume, pvol))
173 			ret = 1;
174 		rcu_read_unlock();
175 	}
176 
177 	afs_put_volume(volume, afs_volume_trace_put_query_alias);
178 	afs_put_volume(pvol, afs_volume_trace_put_query_alias);
179 	return ret;
180 }
181 
182 /*
183  * Query the new cell for volumes we know exist in cells we're already using.
184  */
185 static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
186 {
187 	struct afs_cell *p;
188 
189 	_enter("%s", cell->name);
190 
191 	if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
192 		return -ERESTARTSYS;
193 
194 	hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
195 		if (p == cell || p->alias_of)
196 			continue;
197 		if (RB_EMPTY_ROOT(&p->volumes))
198 			continue;
199 		if (p->root_volume)
200 			continue; /* Ignore cells that have a root.cell volume. */
201 		afs_use_cell(p, afs_cell_trace_use_check_alias);
202 		mutex_unlock(&cell->net->proc_cells_lock);
203 
204 		if (afs_query_for_alias_one(cell, key, p) != 0)
205 			goto is_alias;
206 
207 		if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
208 			afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
209 			return -ERESTARTSYS;
210 		}
211 
212 		afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
213 	}
214 
215 	mutex_unlock(&cell->net->proc_cells_lock);
216 	_leave(" = 0");
217 	return 0;
218 
219 is_alias:
220 	cell->alias_of = p; /* Transfer our ref */
221 	return 1;
222 }
223 
224 /*
225  * Look up a VLDB record for a volume.
226  */
227 static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
228 {
229 	struct afs_vl_cursor vc;
230 	char *cell_name = ERR_PTR(-EDESTADDRREQ);
231 	bool skipped = false, not_skipped = false;
232 	int ret;
233 
234 	if (!afs_begin_vlserver_operation(&vc, cell, key))
235 		return ERR_PTR(-ERESTARTSYS);
236 
237 	while (afs_select_vlserver(&vc)) {
238 		if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
239 			vc.call_error = -EOPNOTSUPP;
240 			skipped = true;
241 			continue;
242 		}
243 		not_skipped = true;
244 		cell_name = afs_yfsvl_get_cell_name(&vc);
245 	}
246 
247 	ret = afs_end_vlserver_operation(&vc);
248 	if (skipped && !not_skipped)
249 		ret = -EOPNOTSUPP;
250 	return ret < 0 ? ERR_PTR(ret) : cell_name;
251 }
252 
253 static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
254 {
255 	struct afs_cell *master;
256 	char *cell_name;
257 
258 	cell_name = afs_vl_get_cell_name(cell, key);
259 	if (IS_ERR(cell_name))
260 		return PTR_ERR(cell_name);
261 
262 	if (strcmp(cell_name, cell->name) == 0) {
263 		kfree(cell_name);
264 		return 0;
265 	}
266 
267 	master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
268 				 NULL, false);
269 	kfree(cell_name);
270 	if (IS_ERR(master))
271 		return PTR_ERR(master);
272 
273 	cell->alias_of = master; /* Transfer our ref */
274 	return 1;
275 }
276 
277 static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
278 {
279 	struct afs_volume *root_volume;
280 	int ret;
281 
282 	_enter("%s", cell->name);
283 
284 	ret = yfs_check_canonical_cell_name(cell, key);
285 	if (ret != -EOPNOTSUPP)
286 		return ret;
287 
288 	/* Try and get the root.cell volume for comparison with other cells */
289 	root_volume = afs_sample_volume(cell, key, "root.cell", 9);
290 	if (!IS_ERR(root_volume)) {
291 		cell->root_volume = root_volume;
292 		return afs_compare_cell_roots(cell);
293 	}
294 
295 	if (PTR_ERR(root_volume) != -ENOMEDIUM)
296 		return PTR_ERR(root_volume);
297 
298 	/* Okay, this cell doesn't have an root.cell volume.  We need to
299 	 * locate some other random volume and use that to check.
300 	 */
301 	return afs_query_for_alias(cell, key);
302 }
303 
304 /*
305  * Check to see if a new cell is an alias of a cell we already have.  At this
306  * point we have the cell's volume server list.
307  *
308  * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
309  * if we had problems gathering the data required.  In the case the we did
310  * detect an alias, cell->alias_of is set to point to the assumed master.
311  */
312 int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
313 {
314 	struct afs_net *net = cell->net;
315 	int ret;
316 
317 	if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
318 		return -ERESTARTSYS;
319 
320 	if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
321 		ret = afs_do_cell_detect_alias(cell, key);
322 		if (ret >= 0)
323 			clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
324 	} else {
325 		ret = cell->alias_of ? 1 : 0;
326 	}
327 
328 	mutex_unlock(&net->cells_alias_lock);
329 
330 	if (ret == 1)
331 		pr_notice("kAFS: Cell %s is an alias of %s\n",
332 			  cell->name, cell->alias_of->name);
333 	return ret;
334 }
335