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(p: &server_a->uuid, q: &server_b->uuid, size: 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(vol_a: cell->root_volume, vol_b: 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(sl: &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(sl: &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, name: pvol->name, namelen: pvol->name_len); |
158 | if (IS_ERR(ptr: volume)) { |
159 | afs_put_volume(volume: pvol, reason: afs_volume_trace_put_query_alias); |
160 | if (PTR_ERR(ptr: volume) != -ENOMEDIUM) |
161 | return PTR_ERR(ptr: 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(vol_a: volume, vol_b: pvol)) |
173 | ret = 1; |
174 | rcu_read_unlock(); |
175 | } |
176 | |
177 | afs_put_volume(volume, reason: afs_volume_trace_put_query_alias); |
178 | afs_put_volume(volume: pvol, reason: 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(lock: &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(lock: &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(error: -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(error: -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(error: 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(ptr: cell_name)) |
260 | return PTR_ERR(ptr: cell_name); |
261 | |
262 | if (strcmp(cell_name, cell->name) == 0) { |
263 | kfree(objp: cell_name); |
264 | return 0; |
265 | } |
266 | |
267 | master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name), |
268 | NULL, false); |
269 | kfree(objp: cell_name); |
270 | if (IS_ERR(ptr: master)) |
271 | return PTR_ERR(ptr: 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, name: "root.cell" , namelen: 9); |
290 | if (!IS_ERR(ptr: root_volume)) { |
291 | cell->root_volume = root_volume; |
292 | return afs_compare_cell_roots(cell); |
293 | } |
294 | |
295 | if (PTR_ERR(ptr: root_volume) != -ENOMEDIUM) |
296 | return PTR_ERR(ptr: 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, addr: &cell->flags); |
324 | } else { |
325 | ret = cell->alias_of ? 1 : 0; |
326 | } |
327 | |
328 | mutex_unlock(lock: &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 | |