1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* AFS cell alias detection
4 * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
5 * Written by David Howells (dhowells@redhat.com)
8 #include <linux/slab.h>
9 #include <linux/sched.h>
10 #include <linux/namei.h>
11 #include <keys/rxrpc-type.h>
17 static struct afs_volume
*afs_sample_volume(struct afs_cell
*cell
, struct key
*key
,
18 const char *name
, unsigned int namelen
)
20 struct afs_volume
*volume
;
21 struct afs_fs_context fc
= {
22 .type
= 0, /* Explicitly leave it to the VLDB */
27 .key
= key
, /* This might need to be something */
30 volume
= afs_create_volume(&fc
);
31 _leave(" = %p", volume
);
36 * Compare the address lists of a pair of fileservers.
38 static int afs_compare_fs_alists(const struct afs_server
*server_a
,
39 const struct afs_server
*server_b
)
41 const struct afs_addr_list
*la
, *lb
;
42 int a
= 0, b
= 0, addr_matches
= 0;
44 la
= rcu_dereference(server_a
->endpoint_state
)->addresses
;
45 lb
= rcu_dereference(server_b
->endpoint_state
)->addresses
;
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
;
54 } else if (diff
> 0) {
67 * Compare the fileserver lists of two volumes. The server lists are sorted in
68 * order of ascending UUID.
70 static int afs_compare_volume_slists(const struct afs_volume
*vol_a
,
71 const struct afs_volume
*vol_b
)
73 const struct afs_server_list
*la
, *lb
;
74 int i
, a
= 0, b
= 0, uuid_matches
= 0, addr_matches
= 0;
76 la
= rcu_dereference(vol_a
->servers
);
77 lb
= rcu_dereference(vol_b
->servers
);
79 for (i
= 0; i
< AFS_MAXTYPES
; i
++)
80 if (vol_a
->vids
[i
] != vol_b
->vids
[i
])
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
));
90 } else if (diff
> 0) {
94 addr_matches
+= afs_compare_fs_alists(server_a
, server_b
);
100 _leave(" = %d [um %d]", addr_matches
, uuid_matches
);
105 * Compare root.cell volumes.
107 static int afs_compare_cell_roots(struct afs_cell
*cell
)
115 hlist_for_each_entry_rcu(p
, &cell
->net
->proc_cells
, proc_link
) {
116 if (p
== cell
|| p
->alias_of
)
119 continue; /* Ignore cells that don't have a root.cell volume. */
121 if (afs_compare_volume_slists(cell
->root_volume
, p
->root_volume
) != 0)
131 cell
->alias_of
= afs_use_cell(p
, afs_cell_trace_use_alias
);
136 * Query the new cell for a volume from a cell we're already using.
138 static int afs_query_for_alias_one(struct afs_cell
*cell
, struct key
*key
,
141 struct afs_volume
*volume
, *pvol
= NULL
;
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
);
154 _enter("%s:%s", cell
->name
, pvol
->name
);
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 */
166 /* The new cell has a like-named volume also - compare volume ID,
167 * server and address lists.
170 if (pvol
->vid
== volume
->vid
) {
172 if (afs_compare_volume_slists(volume
, pvol
))
177 afs_put_volume(volume
, afs_volume_trace_put_query_alias
);
178 afs_put_volume(pvol
, afs_volume_trace_put_query_alias
);
183 * Query the new cell for volumes we know exist in cells we're already using.
185 static int afs_query_for_alias(struct afs_cell
*cell
, struct key
*key
)
189 _enter("%s", cell
->name
);
191 if (mutex_lock_interruptible(&cell
->net
->proc_cells_lock
) < 0)
194 hlist_for_each_entry(p
, &cell
->net
->proc_cells
, proc_link
) {
195 if (p
== cell
|| p
->alias_of
)
197 if (RB_EMPTY_ROOT(&p
->volumes
))
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
);
204 if (afs_query_for_alias_one(cell
, key
, p
) != 0)
207 if (mutex_lock_interruptible(&cell
->net
->proc_cells_lock
) < 0) {
208 afs_unuse_cell(cell
->net
, p
, afs_cell_trace_unuse_check_alias
);
212 afs_unuse_cell(cell
->net
, p
, afs_cell_trace_unuse_check_alias
);
215 mutex_unlock(&cell
->net
->proc_cells_lock
);
220 cell
->alias_of
= p
; /* Transfer our ref */
225 * Look up a VLDB record for a volume.
227 static char *afs_vl_get_cell_name(struct afs_cell
*cell
, struct key
*key
)
229 struct afs_vl_cursor vc
;
230 char *cell_name
= ERR_PTR(-EDESTADDRREQ
);
231 bool skipped
= false, not_skipped
= false;
234 if (!afs_begin_vlserver_operation(&vc
, cell
, key
))
235 return ERR_PTR(-ERESTARTSYS
);
237 while (afs_select_vlserver(&vc
)) {
238 if (!test_bit(AFS_VLSERVER_FL_IS_YFS
, &vc
.server
->flags
)) {
239 vc
.call_error
= -EOPNOTSUPP
;
244 cell_name
= afs_yfsvl_get_cell_name(&vc
);
247 ret
= afs_end_vlserver_operation(&vc
);
248 if (skipped
&& !not_skipped
)
250 return ret
< 0 ? ERR_PTR(ret
) : cell_name
;
253 static int yfs_check_canonical_cell_name(struct afs_cell
*cell
, struct key
*key
)
255 struct afs_cell
*master
;
258 cell_name
= afs_vl_get_cell_name(cell
, key
);
259 if (IS_ERR(cell_name
))
260 return PTR_ERR(cell_name
);
262 if (strcmp(cell_name
, cell
->name
) == 0) {
267 master
= afs_lookup_cell(cell
->net
, cell_name
, strlen(cell_name
),
271 return PTR_ERR(master
);
273 cell
->alias_of
= master
; /* Transfer our ref */
277 static int afs_do_cell_detect_alias(struct afs_cell
*cell
, struct key
*key
)
279 struct afs_volume
*root_volume
;
282 _enter("%s", cell
->name
);
284 ret
= yfs_check_canonical_cell_name(cell
, key
);
285 if (ret
!= -EOPNOTSUPP
)
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
);
295 if (PTR_ERR(root_volume
) != -ENOMEDIUM
)
296 return PTR_ERR(root_volume
);
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.
301 return afs_query_for_alias(cell
, key
);
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.
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.
312 int afs_cell_detect_alias(struct afs_cell
*cell
, struct key
*key
)
314 struct afs_net
*net
= cell
->net
;
317 if (mutex_lock_interruptible(&net
->cells_alias_lock
) < 0)
320 if (test_bit(AFS_CELL_FL_CHECK_ALIAS
, &cell
->flags
)) {
321 ret
= afs_do_cell_detect_alias(cell
, key
);
323 clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS
, &cell
->flags
);
325 ret
= cell
->alias_of
? 1 : 0;
328 mutex_unlock(&net
->cells_alias_lock
);
331 pr_notice("kAFS: Cell %s is an alias of %s\n",
332 cell
->name
, cell
->alias_of
->name
);