linux/fs/afs/vl_alias.c
<<
>>
Prefs
   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 */
  17static 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 two addresses.
  37 */
  38static int afs_compare_addrs(const struct sockaddr_rxrpc *srx_a,
  39                             const struct sockaddr_rxrpc *srx_b)
  40{
  41        short port_a, port_b;
  42        int addr_a, addr_b, diff;
  43
  44        diff = (short)srx_a->transport_type - (short)srx_b->transport_type;
  45        if (diff)
  46                goto out;
  47
  48        switch (srx_a->transport_type) {
  49        case AF_INET: {
  50                const struct sockaddr_in *a = &srx_a->transport.sin;
  51                const struct sockaddr_in *b = &srx_b->transport.sin;
  52                addr_a = ntohl(a->sin_addr.s_addr);
  53                addr_b = ntohl(b->sin_addr.s_addr);
  54                diff = addr_a - addr_b;
  55                if (diff == 0) {
  56                        port_a = ntohs(a->sin_port);
  57                        port_b = ntohs(b->sin_port);
  58                        diff = port_a - port_b;
  59                }
  60                break;
  61        }
  62
  63        case AF_INET6: {
  64                const struct sockaddr_in6 *a = &srx_a->transport.sin6;
  65                const struct sockaddr_in6 *b = &srx_b->transport.sin6;
  66                diff = memcmp(&a->sin6_addr, &b->sin6_addr, 16);
  67                if (diff == 0) {
  68                        port_a = ntohs(a->sin6_port);
  69                        port_b = ntohs(b->sin6_port);
  70                        diff = port_a - port_b;
  71                }
  72                break;
  73        }
  74
  75        default:
  76                WARN_ON(1);
  77                diff = 1;
  78        }
  79
  80out:
  81        return diff;
  82}
  83
  84/*
  85 * Compare the address lists of a pair of fileservers.
  86 */
  87static int afs_compare_fs_alists(const struct afs_server *server_a,
  88                                 const struct afs_server *server_b)
  89{
  90        const struct afs_addr_list *la, *lb;
  91        int a = 0, b = 0, addr_matches = 0;
  92
  93        la = rcu_dereference(server_a->addresses);
  94        lb = rcu_dereference(server_b->addresses);
  95
  96        while (a < la->nr_addrs && b < lb->nr_addrs) {
  97                const struct sockaddr_rxrpc *srx_a = &la->addrs[a];
  98                const struct sockaddr_rxrpc *srx_b = &lb->addrs[b];
  99                int diff = afs_compare_addrs(srx_a, srx_b);
 100
 101                if (diff < 0) {
 102                        a++;
 103                } else if (diff > 0) {
 104                        b++;
 105                } else {
 106                        addr_matches++;
 107                        a++;
 108                        b++;
 109                }
 110        }
 111
 112        return addr_matches;
 113}
 114
 115/*
 116 * Compare the fileserver lists of two volumes.  The server lists are sorted in
 117 * order of ascending UUID.
 118 */
 119static int afs_compare_volume_slists(const struct afs_volume *vol_a,
 120                                     const struct afs_volume *vol_b)
 121{
 122        const struct afs_server_list *la, *lb;
 123        int i, a = 0, b = 0, uuid_matches = 0, addr_matches = 0;
 124
 125        la = rcu_dereference(vol_a->servers);
 126        lb = rcu_dereference(vol_b->servers);
 127
 128        for (i = 0; i < AFS_MAXTYPES; i++)
 129                if (la->vids[i] != lb->vids[i])
 130                        return 0;
 131
 132        while (a < la->nr_servers && b < lb->nr_servers) {
 133                const struct afs_server *server_a = la->servers[a].server;
 134                const struct afs_server *server_b = lb->servers[b].server;
 135                int diff = memcmp(&server_a->uuid, &server_b->uuid, sizeof(uuid_t));
 136
 137                if (diff < 0) {
 138                        a++;
 139                } else if (diff > 0) {
 140                        b++;
 141                } else {
 142                        uuid_matches++;
 143                        addr_matches += afs_compare_fs_alists(server_a, server_b);
 144                        a++;
 145                        b++;
 146                }
 147        }
 148
 149        _leave(" = %d [um %d]", addr_matches, uuid_matches);
 150        return addr_matches;
 151}
 152
 153/*
 154 * Compare root.cell volumes.
 155 */
 156static int afs_compare_cell_roots(struct afs_cell *cell)
 157{
 158        struct afs_cell *p;
 159
 160        _enter("");
 161
 162        rcu_read_lock();
 163
 164        hlist_for_each_entry_rcu(p, &cell->net->proc_cells, proc_link) {
 165                if (p == cell || p->alias_of)
 166                        continue;
 167                if (!p->root_volume)
 168                        continue; /* Ignore cells that don't have a root.cell volume. */
 169
 170                if (afs_compare_volume_slists(cell->root_volume, p->root_volume) != 0)
 171                        goto is_alias;
 172        }
 173
 174        rcu_read_unlock();
 175        _leave(" = 0");
 176        return 0;
 177
 178is_alias:
 179        rcu_read_unlock();
 180        cell->alias_of = afs_use_cell(p, afs_cell_trace_use_alias);
 181        return 1;
 182}
 183
 184/*
 185 * Query the new cell for a volume from a cell we're already using.
 186 */
 187static int afs_query_for_alias_one(struct afs_cell *cell, struct key *key,
 188                                   struct afs_cell *p)
 189{
 190        struct afs_volume *volume, *pvol = NULL;
 191        int ret;
 192
 193        /* Arbitrarily pick a volume from the list. */
 194        read_seqlock_excl(&p->volume_lock);
 195        if (!RB_EMPTY_ROOT(&p->volumes))
 196                pvol = afs_get_volume(rb_entry(p->volumes.rb_node,
 197                                               struct afs_volume, cell_node),
 198                                      afs_volume_trace_get_query_alias);
 199        read_sequnlock_excl(&p->volume_lock);
 200        if (!pvol)
 201                return 0;
 202
 203        _enter("%s:%s", cell->name, pvol->name);
 204
 205        /* And see if it's in the new cell. */
 206        volume = afs_sample_volume(cell, key, pvol->name, pvol->name_len);
 207        if (IS_ERR(volume)) {
 208                afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
 209                if (PTR_ERR(volume) != -ENOMEDIUM)
 210                        return PTR_ERR(volume);
 211                /* That volume is not in the new cell, so not an alias */
 212                return 0;
 213        }
 214
 215        /* The new cell has a like-named volume also - compare volume ID,
 216         * server and address lists.
 217         */
 218        ret = 0;
 219        if (pvol->vid == volume->vid) {
 220                rcu_read_lock();
 221                if (afs_compare_volume_slists(volume, pvol))
 222                        ret = 1;
 223                rcu_read_unlock();
 224        }
 225
 226        afs_put_volume(cell->net, volume, afs_volume_trace_put_query_alias);
 227        afs_put_volume(cell->net, pvol, afs_volume_trace_put_query_alias);
 228        return ret;
 229}
 230
 231/*
 232 * Query the new cell for volumes we know exist in cells we're already using.
 233 */
 234static int afs_query_for_alias(struct afs_cell *cell, struct key *key)
 235{
 236        struct afs_cell *p;
 237
 238        _enter("%s", cell->name);
 239
 240        if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0)
 241                return -ERESTARTSYS;
 242
 243        hlist_for_each_entry(p, &cell->net->proc_cells, proc_link) {
 244                if (p == cell || p->alias_of)
 245                        continue;
 246                if (RB_EMPTY_ROOT(&p->volumes))
 247                        continue;
 248                if (p->root_volume)
 249                        continue; /* Ignore cells that have a root.cell volume. */
 250                afs_use_cell(p, afs_cell_trace_use_check_alias);
 251                mutex_unlock(&cell->net->proc_cells_lock);
 252
 253                if (afs_query_for_alias_one(cell, key, p) != 0)
 254                        goto is_alias;
 255
 256                if (mutex_lock_interruptible(&cell->net->proc_cells_lock) < 0) {
 257                        afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
 258                        return -ERESTARTSYS;
 259                }
 260
 261                afs_unuse_cell(cell->net, p, afs_cell_trace_unuse_check_alias);
 262        }
 263
 264        mutex_unlock(&cell->net->proc_cells_lock);
 265        _leave(" = 0");
 266        return 0;
 267
 268is_alias:
 269        cell->alias_of = p; /* Transfer our ref */
 270        return 1;
 271}
 272
 273/*
 274 * Look up a VLDB record for a volume.
 275 */
 276static char *afs_vl_get_cell_name(struct afs_cell *cell, struct key *key)
 277{
 278        struct afs_vl_cursor vc;
 279        char *cell_name = ERR_PTR(-EDESTADDRREQ);
 280        bool skipped = false, not_skipped = false;
 281        int ret;
 282
 283        if (!afs_begin_vlserver_operation(&vc, cell, key))
 284                return ERR_PTR(-ERESTARTSYS);
 285
 286        while (afs_select_vlserver(&vc)) {
 287                if (!test_bit(AFS_VLSERVER_FL_IS_YFS, &vc.server->flags)) {
 288                        vc.ac.error = -EOPNOTSUPP;
 289                        skipped = true;
 290                        continue;
 291                }
 292                not_skipped = true;
 293                cell_name = afs_yfsvl_get_cell_name(&vc);
 294        }
 295
 296        ret = afs_end_vlserver_operation(&vc);
 297        if (skipped && !not_skipped)
 298                ret = -EOPNOTSUPP;
 299        return ret < 0 ? ERR_PTR(ret) : cell_name;
 300}
 301
 302static int yfs_check_canonical_cell_name(struct afs_cell *cell, struct key *key)
 303{
 304        struct afs_cell *master;
 305        char *cell_name;
 306
 307        cell_name = afs_vl_get_cell_name(cell, key);
 308        if (IS_ERR(cell_name))
 309                return PTR_ERR(cell_name);
 310
 311        if (strcmp(cell_name, cell->name) == 0) {
 312                kfree(cell_name);
 313                return 0;
 314        }
 315
 316        master = afs_lookup_cell(cell->net, cell_name, strlen(cell_name),
 317                                 NULL, false);
 318        kfree(cell_name);
 319        if (IS_ERR(master))
 320                return PTR_ERR(master);
 321
 322        cell->alias_of = master; /* Transfer our ref */
 323        return 1;
 324}
 325
 326static int afs_do_cell_detect_alias(struct afs_cell *cell, struct key *key)
 327{
 328        struct afs_volume *root_volume;
 329        int ret;
 330
 331        _enter("%s", cell->name);
 332
 333        ret = yfs_check_canonical_cell_name(cell, key);
 334        if (ret != -EOPNOTSUPP)
 335                return ret;
 336
 337        /* Try and get the root.cell volume for comparison with other cells */
 338        root_volume = afs_sample_volume(cell, key, "root.cell", 9);
 339        if (!IS_ERR(root_volume)) {
 340                cell->root_volume = root_volume;
 341                return afs_compare_cell_roots(cell);
 342        }
 343
 344        if (PTR_ERR(root_volume) != -ENOMEDIUM)
 345                return PTR_ERR(root_volume);
 346
 347        /* Okay, this cell doesn't have an root.cell volume.  We need to
 348         * locate some other random volume and use that to check.
 349         */
 350        return afs_query_for_alias(cell, key);
 351}
 352
 353/*
 354 * Check to see if a new cell is an alias of a cell we already have.  At this
 355 * point we have the cell's volume server list.
 356 *
 357 * Returns 0 if we didn't detect an alias, 1 if we found an alias and an error
 358 * if we had problems gathering the data required.  In the case the we did
 359 * detect an alias, cell->alias_of is set to point to the assumed master.
 360 */
 361int afs_cell_detect_alias(struct afs_cell *cell, struct key *key)
 362{
 363        struct afs_net *net = cell->net;
 364        int ret;
 365
 366        if (mutex_lock_interruptible(&net->cells_alias_lock) < 0)
 367                return -ERESTARTSYS;
 368
 369        if (test_bit(AFS_CELL_FL_CHECK_ALIAS, &cell->flags)) {
 370                ret = afs_do_cell_detect_alias(cell, key);
 371                if (ret >= 0)
 372                        clear_bit_unlock(AFS_CELL_FL_CHECK_ALIAS, &cell->flags);
 373        } else {
 374                ret = cell->alias_of ? 1 : 0;
 375        }
 376
 377        mutex_unlock(&net->cells_alias_lock);
 378
 379        if (ret == 1)
 380                pr_notice("kAFS: Cell %s is an alias of %s\n",
 381                          cell->name, cell->alias_of->name);
 382        return ret;
 383}
 384