forked from Minki/linux
bf99a53ce2
YFS VL servers offer an upgraded Volume Location service that can return IPv6 addresses to fileservers and volume servers in addition to IPv4 addresses using the YFSVL.GetEndpoints operation which we should use if it's available. To this end: (1) Make rxrpc_kernel_recv_data() return the call's current service ID so that the caller can detect service upgrade and see what the service was upgraded to. (2) When we see a VL server address we haven't seen before, send a VL.GetCapabilities operation to it with the service upgrade bit set. If we get an upgrade to the YFS VL service, change the service ID in the address list for that address to use the upgraded service and set a flag to note that this appears to be a YFS-compatible server. (3) If, when a server's addresses are being looked up, we note that we previously detected a YFS-compatible server, then send the YFSVL.GetEndpoints operation rather than VL.GetAddrsU. (4) Build a fileserver address list from the reply of YFSVL.GetEndpoints, including both IPv4 and IPv6 addresses. Volume server addresses are discarded. (5) The address list is sorted by address and port now, instead of just address. This allows multiple servers on the same host sitting on different ports. Signed-off-by: David Howells <dhowells@redhat.com>
408 lines
9.7 KiB
C
408 lines
9.7 KiB
C
/* AFS volume management
|
|
*
|
|
* Copyright (C) 2002, 2007 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include "internal.h"
|
|
|
|
unsigned __read_mostly afs_volume_gc_delay = 10;
|
|
unsigned __read_mostly afs_volume_record_life = 60 * 60;
|
|
|
|
static const char *const afs_voltypes[] = { "R/W", "R/O", "BAK" };
|
|
|
|
/*
|
|
* Allocate a volume record and load it up from a vldb record.
|
|
*/
|
|
static struct afs_volume *afs_alloc_volume(struct afs_mount_params *params,
|
|
struct afs_vldb_entry *vldb,
|
|
unsigned long type_mask)
|
|
{
|
|
struct afs_server_list *slist;
|
|
struct afs_server *server;
|
|
struct afs_volume *volume;
|
|
int ret = -ENOMEM, nr_servers = 0, i, j;
|
|
|
|
for (i = 0; i < vldb->nr_servers; i++)
|
|
if (vldb->fs_mask[i] & type_mask)
|
|
nr_servers++;
|
|
|
|
volume = kzalloc(sizeof(struct afs_volume), GFP_KERNEL);
|
|
if (!volume)
|
|
goto error_0;
|
|
|
|
volume->vid = vldb->vid[params->type];
|
|
volume->update_at = ktime_get_real_seconds() + afs_volume_record_life;
|
|
volume->cell = afs_get_cell(params->cell);
|
|
volume->type = params->type;
|
|
volume->type_force = params->force;
|
|
volume->name_len = vldb->name_len;
|
|
|
|
atomic_set(&volume->usage, 1);
|
|
INIT_LIST_HEAD(&volume->proc_link);
|
|
rwlock_init(&volume->servers_lock);
|
|
memcpy(volume->name, vldb->name, vldb->name_len + 1);
|
|
|
|
slist = afs_alloc_server_list(params->cell, params->key, vldb, type_mask);
|
|
if (IS_ERR(slist)) {
|
|
ret = PTR_ERR(slist);
|
|
goto error_1;
|
|
}
|
|
|
|
refcount_set(&slist->usage, 1);
|
|
volume->servers = slist;
|
|
|
|
/* Make sure a records exists for each server this volume occupies. */
|
|
for (i = 0; i < nr_servers; i++) {
|
|
if (!(vldb->fs_mask[i] & type_mask))
|
|
continue;
|
|
|
|
server = afs_lookup_server(params->cell, params->key,
|
|
&vldb->fs_server[i]);
|
|
if (IS_ERR(server)) {
|
|
ret = PTR_ERR(server);
|
|
if (ret == -ENOENT)
|
|
continue;
|
|
goto error_2;
|
|
}
|
|
|
|
/* Insertion-sort by server pointer */
|
|
for (j = 0; j < slist->nr_servers; j++)
|
|
if (slist->servers[j].server >= server)
|
|
break;
|
|
if (j < slist->nr_servers) {
|
|
if (slist->servers[j].server == server) {
|
|
afs_put_server(params->net, server);
|
|
continue;
|
|
}
|
|
|
|
memmove(slist->servers + j + 1,
|
|
slist->servers + j,
|
|
(slist->nr_servers - j) * sizeof(struct afs_server_entry));
|
|
}
|
|
|
|
slist->servers[j].server = server;
|
|
slist->nr_servers++;
|
|
}
|
|
|
|
if (slist->nr_servers == 0) {
|
|
ret = -EDESTADDRREQ;
|
|
goto error_2;
|
|
}
|
|
|
|
return volume;
|
|
|
|
error_2:
|
|
afs_put_serverlist(params->net, slist);
|
|
error_1:
|
|
kfree(volume);
|
|
error_0:
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* Look up a VLDB record for a volume.
|
|
*/
|
|
static struct afs_vldb_entry *afs_vl_lookup_vldb(struct afs_cell *cell,
|
|
struct key *key,
|
|
const char *volname,
|
|
size_t volnamesz)
|
|
{
|
|
struct afs_addr_cursor ac;
|
|
struct afs_vldb_entry *vldb;
|
|
int ret;
|
|
|
|
ret = afs_set_vl_cursor(&ac, cell);
|
|
if (ret < 0)
|
|
return ERR_PTR(ret);
|
|
|
|
while (afs_iterate_addresses(&ac)) {
|
|
if (!test_bit(ac.index, &ac.alist->probed)) {
|
|
ret = afs_vl_get_capabilities(cell->net, &ac, key);
|
|
switch (ret) {
|
|
case VL_SERVICE:
|
|
clear_bit(ac.index, &ac.alist->yfs);
|
|
set_bit(ac.index, &ac.alist->probed);
|
|
ac.addr->srx_service = ret;
|
|
break;
|
|
case YFS_VL_SERVICE:
|
|
set_bit(ac.index, &ac.alist->yfs);
|
|
set_bit(ac.index, &ac.alist->probed);
|
|
ac.addr->srx_service = ret;
|
|
break;
|
|
}
|
|
}
|
|
|
|
vldb = afs_vl_get_entry_by_name_u(cell->net, &ac, key,
|
|
volname, volnamesz);
|
|
switch (ac.error) {
|
|
case 0:
|
|
afs_end_cursor(&ac);
|
|
return vldb;
|
|
case -ECONNABORTED:
|
|
ac.error = afs_abort_to_error(ac.abort_code);
|
|
goto error;
|
|
case -ENOMEM:
|
|
case -ENONET:
|
|
goto error;
|
|
case -ENETUNREACH:
|
|
case -EHOSTUNREACH:
|
|
case -ECONNREFUSED:
|
|
break;
|
|
default:
|
|
ac.error = -EIO;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
error:
|
|
return ERR_PTR(afs_end_cursor(&ac));
|
|
}
|
|
|
|
/*
|
|
* Look up a volume in the VL server and create a candidate volume record for
|
|
* it.
|
|
*
|
|
* The volume name can be one of the following:
|
|
* "%[cell:]volume[.]" R/W volume
|
|
* "#[cell:]volume[.]" R/O or R/W volume (rwparent=0),
|
|
* or R/W (rwparent=1) volume
|
|
* "%[cell:]volume.readonly" R/O volume
|
|
* "#[cell:]volume.readonly" R/O volume
|
|
* "%[cell:]volume.backup" Backup volume
|
|
* "#[cell:]volume.backup" Backup volume
|
|
*
|
|
* The cell name is optional, and defaults to the current cell.
|
|
*
|
|
* See "The Rules of Mount Point Traversal" in Chapter 5 of the AFS SysAdmin
|
|
* Guide
|
|
* - Rule 1: Explicit type suffix forces access of that type or nothing
|
|
* (no suffix, then use Rule 2 & 3)
|
|
* - Rule 2: If parent volume is R/O, then mount R/O volume by preference, R/W
|
|
* if not available
|
|
* - Rule 3: If parent volume is R/W, then only mount R/W volume unless
|
|
* explicitly told otherwise
|
|
*/
|
|
struct afs_volume *afs_create_volume(struct afs_mount_params *params)
|
|
{
|
|
struct afs_vldb_entry *vldb;
|
|
struct afs_volume *volume;
|
|
unsigned long type_mask = 1UL << params->type;
|
|
|
|
vldb = afs_vl_lookup_vldb(params->cell, params->key,
|
|
params->volname, params->volnamesz);
|
|
if (IS_ERR(vldb))
|
|
return ERR_CAST(vldb);
|
|
|
|
if (test_bit(AFS_VLDB_QUERY_ERROR, &vldb->flags)) {
|
|
volume = ERR_PTR(vldb->error);
|
|
goto error;
|
|
}
|
|
|
|
/* Make the final decision on the type we want */
|
|
volume = ERR_PTR(-ENOMEDIUM);
|
|
if (params->force) {
|
|
if (!(vldb->flags & type_mask))
|
|
goto error;
|
|
} else if (test_bit(AFS_VLDB_HAS_RO, &vldb->flags)) {
|
|
params->type = AFSVL_ROVOL;
|
|
} else if (test_bit(AFS_VLDB_HAS_RW, &vldb->flags)) {
|
|
params->type = AFSVL_RWVOL;
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
type_mask = 1UL << params->type;
|
|
volume = afs_alloc_volume(params, vldb, type_mask);
|
|
|
|
error:
|
|
kfree(vldb);
|
|
return volume;
|
|
}
|
|
|
|
/*
|
|
* Destroy a volume record
|
|
*/
|
|
static void afs_destroy_volume(struct afs_net *net, struct afs_volume *volume)
|
|
{
|
|
_enter("%p", volume);
|
|
|
|
#ifdef CONFIG_AFS_FSCACHE
|
|
ASSERTCMP(volume->cache, ==, NULL);
|
|
#endif
|
|
|
|
afs_put_serverlist(net, volume->servers);
|
|
afs_put_cell(net, volume->cell);
|
|
kfree(volume);
|
|
|
|
_leave(" [destroyed]");
|
|
}
|
|
|
|
/*
|
|
* Drop a reference on a volume record.
|
|
*/
|
|
void afs_put_volume(struct afs_cell *cell, struct afs_volume *volume)
|
|
{
|
|
if (volume) {
|
|
_enter("%s", volume->name);
|
|
|
|
if (atomic_dec_and_test(&volume->usage))
|
|
afs_destroy_volume(cell->net, volume);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Activate a volume.
|
|
*/
|
|
void afs_activate_volume(struct afs_volume *volume)
|
|
{
|
|
#ifdef CONFIG_AFS_FSCACHE
|
|
volume->cache = fscache_acquire_cookie(volume->cell->cache,
|
|
&afs_volume_cache_index_def,
|
|
volume, true);
|
|
#endif
|
|
|
|
write_lock(&volume->cell->proc_lock);
|
|
list_add_tail(&volume->proc_link, &volume->cell->proc_volumes);
|
|
write_unlock(&volume->cell->proc_lock);
|
|
}
|
|
|
|
/*
|
|
* Deactivate a volume.
|
|
*/
|
|
void afs_deactivate_volume(struct afs_volume *volume)
|
|
{
|
|
_enter("%s", volume->name);
|
|
|
|
write_lock(&volume->cell->proc_lock);
|
|
list_del_init(&volume->proc_link);
|
|
write_unlock(&volume->cell->proc_lock);
|
|
|
|
#ifdef CONFIG_AFS_FSCACHE
|
|
fscache_relinquish_cookie(volume->cache,
|
|
test_bit(AFS_VOLUME_DELETED, &volume->flags));
|
|
volume->cache = NULL;
|
|
#endif
|
|
|
|
_leave("");
|
|
}
|
|
|
|
/*
|
|
* Query the VL service to update the volume status.
|
|
*/
|
|
static int afs_update_volume_status(struct afs_volume *volume, struct key *key)
|
|
{
|
|
struct afs_server_list *new, *old, *discard;
|
|
struct afs_vldb_entry *vldb;
|
|
char idbuf[16];
|
|
int ret, idsz;
|
|
|
|
_enter("");
|
|
|
|
/* We look up an ID by passing it as a decimal string in the
|
|
* operation's name parameter.
|
|
*/
|
|
idsz = sprintf(idbuf, "%u", volume->vid);
|
|
|
|
vldb = afs_vl_lookup_vldb(volume->cell, key, idbuf, idsz);
|
|
if (IS_ERR(vldb)) {
|
|
ret = PTR_ERR(vldb);
|
|
goto error;
|
|
}
|
|
|
|
/* See if the volume got renamed. */
|
|
if (vldb->name_len != volume->name_len ||
|
|
memcmp(vldb->name, volume->name, vldb->name_len) != 0) {
|
|
/* TODO: Use RCU'd string. */
|
|
memcpy(volume->name, vldb->name, AFS_MAXVOLNAME);
|
|
volume->name_len = vldb->name_len;
|
|
}
|
|
|
|
/* See if the volume's server list got updated. */
|
|
new = afs_alloc_server_list(volume->cell, key,
|
|
vldb, (1 << volume->type));
|
|
if (IS_ERR(new)) {
|
|
ret = PTR_ERR(new);
|
|
goto error_vldb;
|
|
}
|
|
|
|
write_lock(&volume->servers_lock);
|
|
|
|
discard = new;
|
|
old = volume->servers;
|
|
if (afs_annotate_server_list(new, old)) {
|
|
new->seq = volume->servers_seq + 1;
|
|
volume->servers = new;
|
|
smp_wmb();
|
|
volume->servers_seq++;
|
|
discard = old;
|
|
}
|
|
|
|
volume->update_at = ktime_get_real_seconds() + afs_volume_record_life;
|
|
clear_bit(AFS_VOLUME_NEEDS_UPDATE, &volume->flags);
|
|
write_unlock(&volume->servers_lock);
|
|
ret = 0;
|
|
|
|
afs_put_serverlist(volume->cell->net, discard);
|
|
error_vldb:
|
|
kfree(vldb);
|
|
error:
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Make sure the volume record is up to date.
|
|
*/
|
|
int afs_check_volume_status(struct afs_volume *volume, struct key *key)
|
|
{
|
|
time64_t now = ktime_get_real_seconds();
|
|
int ret, retries = 0;
|
|
|
|
_enter("");
|
|
|
|
if (volume->update_at <= now)
|
|
set_bit(AFS_VOLUME_NEEDS_UPDATE, &volume->flags);
|
|
|
|
retry:
|
|
if (!test_bit(AFS_VOLUME_NEEDS_UPDATE, &volume->flags) &&
|
|
!test_bit(AFS_VOLUME_WAIT, &volume->flags)) {
|
|
_leave(" = 0");
|
|
return 0;
|
|
}
|
|
|
|
if (!test_and_set_bit_lock(AFS_VOLUME_UPDATING, &volume->flags)) {
|
|
ret = afs_update_volume_status(volume, key);
|
|
clear_bit_unlock(AFS_VOLUME_WAIT, &volume->flags);
|
|
clear_bit_unlock(AFS_VOLUME_UPDATING, &volume->flags);
|
|
wake_up_bit(&volume->flags, AFS_VOLUME_WAIT);
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (!test_bit(AFS_VOLUME_WAIT, &volume->flags)) {
|
|
_leave(" = 0 [no wait]");
|
|
return 0;
|
|
}
|
|
|
|
ret = wait_on_bit(&volume->flags, AFS_VOLUME_WAIT, TASK_INTERRUPTIBLE);
|
|
if (ret == -ERESTARTSYS) {
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
retries++;
|
|
if (retries == 4) {
|
|
_leave(" = -ESTALE");
|
|
return -ESTALE;
|
|
}
|
|
goto retry;
|
|
}
|