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