123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /* AFS cell alias detection
- *
- * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
- * Written by David Howells ([email protected])
- */
- #include <linux/slab.h>
- #include <linux/sched.h>
- #include <linux/namei.h>
- #include <keys/rxrpc-type.h>
- #include "internal.h"
- /*
- * Sample a volume.
- */
- static struct afs_volume *afs_sample_volume(struct afs_cell *cell, struct key *key,
- const char *name, unsigned int namelen)
- {
- struct afs_volume *volume;
- struct afs_fs_context fc = {
- .type = 0, /* Explicitly leave it to the VLDB */
- .volnamesz = namelen,
- .volname = name,
- .net = cell->net,
- .cell = cell,
- .key = key, /* This might need to be something */
- };
- volume = afs_create_volume(&fc);
- _leave(" = %p", volume);
- return volume;
- }
- /*
- * Compare two addresses.
- */
- static int afs_compare_addrs(const struct sockaddr_rxrpc *srx_a,
- const struct sockaddr_rxrpc *srx_b)
- {
- short port_a, port_b;
- int addr_a, addr_b, diff;
- diff = (short)srx_a->transport_type - (short)srx_b->transport_type;
- if (diff)
- goto out;
- switch (srx_a->transport_type) {
- case AF_INET: {
- const struct sockaddr_in *a = &srx_a->transport.sin;
- const struct sockaddr_in *b = &srx_b->transport.sin;
- addr_a = ntohl(a->sin_addr.s_addr);
- addr_b = ntohl(b->sin_addr.s_addr);
- diff = addr_a - addr_b;
- if (diff == 0) {
- port_a = ntohs(a->sin_port);
- port_b = ntohs(b->sin_port);
- diff = port_a - port_b;
- }
- break;
- }
- case AF_INET6: {
- const struct sockaddr_in6 *a = &srx_a->transport.sin6;
- const struct sockaddr_in6 *b = &srx_b->transport.sin6;
- diff = memcmp(&a->sin6_addr, &b->sin6_addr, 16);
- if (diff == 0) {
- port_a = ntohs(a->sin6_port);
- port_b = ntohs(b->sin6_port);
- diff = port_a - port_b;
- }
- break;
- }
- default:
- WARN_ON(1);
- diff = 1;
- }
- out:
- return diff;
- }
- /*
- * Compare the address lists of a pair of fileservers.
- */
- static int afs_compare_fs_alists(const struct afs_server *server_a,
- const struct afs_server *server_b)
- {
- const struct afs_addr_list *la, *lb;
- int a = 0, b = 0, addr_matches = 0;
- la = rcu_dereference(server_a->addresses);
- lb = rcu_dereference(server_b->addresses);
- while (a < la->nr_addrs && b < lb->nr_addrs) {
- const struct sockaddr_rxrpc *srx_a = &la->addrs[a];
- const struct sockaddr_rxrpc *srx_b = &lb->addrs[b];
- int diff = afs_compare_addrs(srx_a, srx_b);
- if (diff < 0) {
- a++;
- } else if (diff > 0) {
- b++;
- } else {
- addr_matches++;
- a++;
- b++;
- }
- }
- return addr_matches;
- }
- /*
- * Compare the fileserver lists of two volumes. The server lists are sorted in
- * order of ascending UUID.
- */
- static int afs_compare_volume_slists(const struct afs_volume *vol_a,
- const struct afs_volume *vol_b)
- {
- const struct afs_server_list *la, *lb;
- int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
- la = rcu_dereference(vol_a->servers);
- lb = rcu_dereference(vol_b->servers);
- for (i = 0; i < AFS_MAXTYPES; i++)
- if (la->vids[i] != lb->vids[i])
- return 0;
- while (a < la->nr_servers && b < lb->nr_servers) {
- const struct afs_server *server_a = la->servers[a].server;
- const struct afs_server *server_b = lb->servers[b].server;
- int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
- if (diff < 0) {
- a++;
- } else if (diff > 0) {
- b++;
- } else {
- uuid_matches++;
- addr_matches += afs_compare_fs_alists(server_a, server_b);
- a++;
- b++;
- }
- }
- _leave(" = %d [um %d]", addr_matches, uuid_matches);
- return addr_matches;
- }
- /*
- * Compare root.cell volumes.
- */
- static int afs_compare_cell_roots(struct afs_cell *cell)
- {
- struct afs_cell *p;
- _enter("");
- rcu_read_lock();
- hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
- if (p == cell || p->alias_of)
- continue;
- if (!p->root_volume)
- continue; /* Ignore cells that don't have a root.cell volume. */
- if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
- goto is_alias;
- }
- rcu_read_unlock();
- _leave(" = 0");
- return 0;
- is_alias:
- rcu_read_unlock();
- cell->alias_of = afs_use_cell(p, afs_cell_trace_use_alias);
- return 1;
- }
- /*
- * Query the new cell for a volume from a cell we're already using.
- */
- static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
- struct afs_cell *p)
- {
- struct afs_volume *volume, *pvol = NULL;
- int ret;
- /* Arbitrarily pick a volume from the list. */
- read_seqlock_excl(&p->volume_lock);
- if (!RB_EMPTY_ROOT(&p->volumes))
- pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
- struct afs_volume, cell_node),
- afs_volume_trace_get_query_alias);
- read_sequnlock_excl(&p->volume_lock);
- if (!pvol)
- return 0;
- _enter("%s:%s", cell->name, pvol->name);
- /* And see if it's in the new cell. */
- volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
- if (IS_ERR(volume)) {
- afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
- if (PTR_ERR(volume) != -ENOMEDIUM)
- return PTR_ERR(volume);
- /* That volume is not in the new cell, so not an alias */
- return 0;
- }
- /* The new cell has a like-named volume also - compare volume ID,
- * server and address lists.
- */
- ret = 0;
- if (pvol->vid == volume->vid) {
- rcu_read_lock();
- if (afs_compare_volume_slists(volume, pvol))
- ret = 1;
- rcu_read_unlock();
- }
- afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias);
- afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
- return ret;
- }
- /*
- * Query the new cell for volumes we know exist in cells we're already using.
- */
- static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
- {
- struct afs_cell *p;
- _enter("%s", cell->name);
- if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
- return -ERESTARTSYS;
- hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
- if (p == cell || p->alias_of)
- continue;
- if (RB_EMPTY_ROOT(&p->volumes))
- continue;
- if (p->root_volume)
- continue; /* Ignore cells that have a root.cell volume. */
- afs_use_cell(p, afs_cell_trace_use_check_alias);
- mutex_unlock(&cell->net->proc_cells_lock);
- if (afs_query_for_alias_one(cell, key, p) != 0)
- goto is_alias;
- if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
- afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
- return -ERESTARTSYS;
- }
- afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
- }
- mutex_unlock(&cell->net->proc_cells_lock);
- _leave(" = 0");
- return 0;
- is_alias:
- cell->alias_of = p; /* Transfer our ref */
- return 1;
- }
- /*
- * Look up a VLDB record for a volume.
- */
- static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
- {
- struct afs_vl_cursor vc;
- char *cell_name = ERR_PTR(-EDESTADDRREQ);
- bool skipped = false, not_skipped = false;
- int ret;
- if (!afs_begin_vlserver_operation(&vc, cell, key))
- return ERR_PTR(-ERESTARTSYS);
- while (afs_select_vlserver(&vc)) {
- if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
- vc.ac.error = -EOPNOTSUPP;
- skipped = true;
- continue;
- }
- not_skipped = true;
- cell_name = afs_yfsvl_get_cell_name(&vc);
- }
- ret = afs_end_vlserver_operation(&vc);
- if (skipped && !not_skipped)
- ret = -EOPNOTSUPP;
- return ret < 0 ? ERR_PTR(ret) : cell_name;
- }
- static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
- {
- struct afs_cell *master;
- char *cell_name;
- cell_name = afs_vl_get_cell_name(cell, key);
- if (IS_ERR(cell_name))
- return PTR_ERR(cell_name);
- if (strcmp(cell_name, cell->name) == 0) {
- kfree(cell_name);
- return 0;
- }
- master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
- NULL, false);
- kfree(cell_name);
- if (IS_ERR(master))
- return PTR_ERR(master);
- cell->alias_of = master; /* Transfer our ref */
- return 1;
- }
- static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
- {
- struct afs_volume *root_volume;
- int ret;
- _enter("%s", cell->name);
- ret = yfs_check_canonical_cell_name(cell, key);
- if (ret != -EOPNOTSUPP)
- return ret;
- /* Try and get the root.cell volume for comparison with other cells */
- root_volume = afs_sample_volume(cell, key, "root.cell", 9);
- if (!IS_ERR(root_volume)) {
- cell->root_volume = root_volume;
- return afs_compare_cell_roots(cell);
- }
- if (PTR_ERR(root_volume) != -ENOMEDIUM)
- return PTR_ERR(root_volume);
- /* Okay, this cell doesn't have an root.cell volume. We need to
- * locate some other random volume and use that to check.
- */
- return afs_query_for_alias(cell, key);
- }
- /*
- * Check to see if a new cell is an alias of a cell we already have. At this
- * point we have the cell's volume server list.
- *
- * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
- * if we had problems gathering the data required. In the case the we did
- * detect an alias, cell->alias_of is set to point to the assumed master.
- */
- int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
- {
- struct afs_net *net = cell->net;
- int ret;
- if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
- return -ERESTARTSYS;
- if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
- ret = afs_do_cell_detect_alias(cell, key);
- if (ret >= 0)
- clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
- } else {
- ret = cell->alias_of ? 1 : 0;
- }
- mutex_unlock(&net->cells_alias_lock);
- if (ret == 1)
- pr_notice("kAFS: Cell %s is an alias of %s\n",
- cell->name, cell->alias_of->name);
- return ret;
- }
|