Commit 0a5143f2 authored by David Howells's avatar David Howells

afs: Implement VL server rotation

Track VL servers as independent entities rather than lumping all their
addresses together into one set and implement server-level rotation by:

 (1) Add the concept of a VL server list, where each server has its own
     separate address list.  This code is similar to the FS server list.

 (2) Use the DNS resolver to retrieve a set of servers and their associated
     addresses, ports, preference and weight ratings.

 (3) In the case of a legacy DNS resolver or an address list given directly
     through /proc/net/afs/cells, create a list containing just a dummy
     server record and attach all the addresses to that.

 (4) Implement a simple rotation policy, for the moment ignoring the
     priorities and weights assigned to the servers.

 (5) Show the address list through /proc/net/afs/<cell>/vlservers.  This
     also displays the source and status of the data as indicated by the
     upcall.
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
parent e7f680f4
......@@ -29,6 +29,8 @@ kafs-y := \
super.o \
netdevices.o \
vlclient.o \
vl_rotate.o \
vl_list.o \
volume.o \
write.o \
xattr.o
......
......@@ -64,19 +64,25 @@ struct afs_addr_list *afs_alloc_addrlist(unsigned int nr,
/*
* Parse a text string consisting of delimited addresses.
*/
struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
struct afs_vlserver_list *afs_parse_text_addrs(struct afs_net *net,
const char *text, size_t len,
char delim,
unsigned short service,
unsigned short port)
{
struct afs_vlserver_list *vllist;
struct afs_addr_list *alist;
const char *p, *end = text + len;
const char *problem;
unsigned int nr = 0;
int ret = -ENOMEM;
_enter("%*.*s,%c", (int)len, (int)len, text, delim);
if (!len)
if (!len) {
_leave(" = -EDESTADDRREQ [empty]");
return ERR_PTR(-EDESTADDRREQ);
}
if (delim == ':' && (memchr(text, ',', len) || !memchr(text, '.', len)))
delim = ',';
......@@ -84,18 +90,24 @@ struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
/* Count the addresses */
p = text;
do {
if (!*p)
return ERR_PTR(-EINVAL);
if (!*p) {
problem = "nul";
goto inval;
}
if (*p == delim)
continue;
nr++;
if (*p == '[') {
p++;
if (p == end)
return ERR_PTR(-EINVAL);
if (p == end) {
problem = "brace1";
goto inval;
}
p = memchr(p, ']', end - p);
if (!p)
return ERR_PTR(-EINVAL);
if (!p) {
problem = "brace2";
goto inval;
}
p++;
if (p >= end)
break;
......@@ -109,10 +121,19 @@ struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
_debug("%u/%u addresses", nr, AFS_MAX_ADDRESSES);
alist = afs_alloc_addrlist(nr, service, port);
if (!alist)
vllist = afs_alloc_vlserver_list(1);
if (!vllist)
return ERR_PTR(-ENOMEM);
vllist->nr_servers = 1;
vllist->servers[0].server = afs_alloc_vlserver("<dummy>", 7, AFS_VL_PORT);
if (!vllist->servers[0].server)
goto error_vl;
alist = afs_alloc_addrlist(nr, service, AFS_VL_PORT);
if (!alist)
goto error;
/* Extract the addresses */
p = text;
do {
......@@ -135,17 +156,21 @@ struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
break;
}
if (in4_pton(p, q - p, (u8 *)&x[0], -1, &stop))
if (in4_pton(p, q - p, (u8 *)&x[0], -1, &stop)) {
family = AF_INET;
else if (in6_pton(p, q - p, (u8 *)x, -1, &stop))
} else if (in6_pton(p, q - p, (u8 *)x, -1, &stop)) {
family = AF_INET6;
else
} else {
problem = "family";
goto bad_address;
}
if (stop != q)
p = q;
if (stop != p) {
problem = "nostop";
goto bad_address;
}
p = q;
if (q < end && *q == ']')
p++;
......@@ -154,18 +179,23 @@ struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
/* Port number specification "+1234" */
xport = 0;
p++;
if (p >= end || !isdigit(*p))
if (p >= end || !isdigit(*p)) {
problem = "port";
goto bad_address;
}
do {
xport *= 10;
xport += *p - '0';
if (xport > 65535)
if (xport > 65535) {
problem = "pval";
goto bad_address;
}
p++;
} while (p < end && isdigit(*p));
} else if (*p == delim) {
p++;
} else {
problem = "weird";
goto bad_address;
}
}
......@@ -177,12 +207,23 @@ struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
} while (p < end);
rcu_assign_pointer(vllist->servers[0].server->addresses, alist);
_leave(" = [nr %u]", alist->nr_addrs);
return alist;
return vllist;
bad_address:
kfree(alist);
inval:
_leave(" = -EINVAL [%s %zu %*.*s]",
problem, p - text, (int)len, (int)len, text);
return ERR_PTR(-EINVAL);
bad_address:
_leave(" = -EINVAL [%s %zu %*.*s]",
problem, p - text, (int)len, (int)len, text);
ret = -EINVAL;
error:
afs_put_addrlist(alist);
error_vl:
afs_put_vlserverlist(net, vllist);
return ERR_PTR(ret);
}
/*
......@@ -201,30 +242,34 @@ static int afs_cmp_addr_list(const struct afs_addr_list *a1,
/*
* Perform a DNS query for VL servers and build a up an address list.
*/
struct afs_addr_list *afs_dns_query(struct afs_cell *cell, time64_t *_expiry)
struct afs_vlserver_list *afs_dns_query(struct afs_cell *cell, time64_t *_expiry)
{
struct afs_addr_list *alist;
char *vllist = NULL;
struct afs_vlserver_list *vllist;
char *result = NULL;
int ret;
_enter("%s", cell->name);
ret = dns_query("afsdb", cell->name, cell->name_len,
"", &vllist, _expiry);
if (ret < 0)
ret = dns_query("afsdb", cell->name, cell->name_len, "srv=1",
&result, _expiry);
if (ret < 0) {
_leave(" = %d [dns]", ret);
return ERR_PTR(ret);
}
if (*_expiry == 0)
*_expiry = ktime_get_real_seconds() + 60;
alist = afs_parse_text_addrs(vllist, strlen(vllist), ',',
if (ret > 1 && result[0] == 0)
vllist = afs_extract_vlserver_list(cell, result, ret);
else
vllist = afs_parse_text_addrs(cell->net, result, ret, ',',
VL_SERVICE, AFS_VL_PORT);
if (IS_ERR(alist)) {
kfree(vllist);
if (alist != ERR_PTR(-ENOMEM))
pr_err("Failed to parse DNS data\n");
return alist;
}
kfree(result);
if (IS_ERR(vllist) && vllist != ERR_PTR(-ENOMEM))
pr_err("Failed to parse DNS data %ld\n", PTR_ERR(vllist));
kfree(vllist);
return alist;
return vllist;
}
/*
......@@ -347,43 +392,3 @@ int afs_end_cursor(struct afs_addr_cursor *ac)
ac->begun = false;
return ac->error;
}
/*
* Set the address cursor for iterating over VL servers.
*/
int afs_set_vl_cursor(struct afs_addr_cursor *ac, struct afs_cell *cell)
{
struct afs_addr_list *alist;
int ret;
if (!rcu_access_pointer(cell->vl_addrs)) {
ret = wait_on_bit(&cell->flags, AFS_CELL_FL_NO_LOOKUP_YET,
TASK_INTERRUPTIBLE);
if (ret < 0)
return ret;
if (!rcu_access_pointer(cell->vl_addrs) &&
ktime_get_real_seconds() < cell->dns_expiry)
return cell->error;
}
read_lock(&cell->vl_addrs_lock);
alist = rcu_dereference_protected(cell->vl_addrs,
lockdep_is_held(&cell->vl_addrs_lock));
if (alist->nr_addrs > 0)
afs_get_addrlist(alist);
else
alist = NULL;
read_unlock(&cell->vl_addrs_lock);
if (!alist)
return -EDESTADDRREQ;
ac->alist = alist;
ac->addr = NULL;
ac->start = READ_ONCE(alist->index);
ac->index = ac->start;
ac->error = 0;
ac->begun = false;
return 0;
}
......@@ -119,7 +119,7 @@ struct afs_cell *afs_lookup_cell_rcu(struct afs_net *net,
*/
static struct afs_cell *afs_alloc_cell(struct afs_net *net,
const char *name, unsigned int namelen,
const char *vllist)
const char *addresses)
{
struct afs_cell *cell;
int i, ret;
......@@ -134,7 +134,7 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
if (namelen == 5 && memcmp(name, "@cell", 5) == 0)
return ERR_PTR(-EINVAL);
_enter("%*.*s,%s", namelen, namelen, name, vllist);
_enter("%*.*s,%s", namelen, namelen, name, addresses);
cell = kzalloc(sizeof(struct afs_cell), GFP_KERNEL);
if (!cell) {
......@@ -153,22 +153,23 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
(1 << AFS_CELL_FL_NO_LOOKUP_YET));
INIT_LIST_HEAD(&cell->proc_volumes);
rwlock_init(&cell->proc_lock);
rwlock_init(&cell->vl_addrs_lock);
rwlock_init(&cell->vl_servers_lock);
/* Fill in the VL server list if we were given a list of addresses to
* use.
*/
if (vllist) {
struct afs_addr_list *alist;
if (addresses) {
struct afs_vlserver_list *vllist;
alist = afs_parse_text_addrs(vllist, strlen(vllist), ':',
vllist = afs_parse_text_addrs(net,
addresses, strlen(addresses), ':',
VL_SERVICE, AFS_VL_PORT);
if (IS_ERR(alist)) {
ret = PTR_ERR(alist);
if (IS_ERR(vllist)) {
ret = PTR_ERR(vllist);
goto parse_failed;
}
rcu_assign_pointer(cell->vl_addrs, alist);
rcu_assign_pointer(cell->vl_servers, vllist);
cell->dns_expiry = TIME64_MAX;
}
......@@ -356,14 +357,14 @@ int afs_cell_init(struct afs_net *net, const char *rootcell)
*/
static void afs_update_cell(struct afs_cell *cell)
{
struct afs_addr_list *alist, *old;
struct afs_vlserver_list *vllist, *old;
time64_t now, expiry;
_enter("%s", cell->name);
alist = afs_dns_query(cell, &expiry);
if (IS_ERR(alist)) {
switch (PTR_ERR(alist)) {
vllist = afs_dns_query(cell, &expiry);
if (IS_ERR(vllist)) {
switch (PTR_ERR(vllist)) {
case -ENODATA:
/* The DNS said that the cell does not exist */
set_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags);
......@@ -387,12 +388,12 @@ static void afs_update_cell(struct afs_cell *cell)
/* Exclusion on changing vl_addrs is achieved by a
* non-reentrant work item.
*/
old = rcu_dereference_protected(cell->vl_addrs, true);
rcu_assign_pointer(cell->vl_addrs, alist);
old = rcu_dereference_protected(cell->vl_servers, true);
rcu_assign_pointer(cell->vl_servers, vllist);
cell->dns_expiry = expiry;
if (old)
afs_put_addrlist(old);
afs_put_vlserverlist(cell->net, old);
}
if (test_and_clear_bit(AFS_CELL_FL_NO_LOOKUP_YET, &cell->flags))
......@@ -414,7 +415,7 @@ static void afs_cell_destroy(struct rcu_head *rcu)
ASSERTCMP(atomic_read(&cell->usage), ==, 0);
afs_put_addrlist(rcu_access_pointer(cell->vl_addrs));
afs_put_vlserverlist(cell->net, rcu_access_pointer(cell->vl_servers));
key_put(cell->anonymous_key);
kfree(cell);
......
......@@ -46,7 +46,7 @@ static int afs_probe_cell_name(struct dentry *dentry)
return 0;
}
ret = dns_query("afsdb", name, len, "", NULL, NULL);
ret = dns_query("afsdb", name, len, "srv=1", NULL, NULL);
if (ret == -ENODATA)
ret = -EDESTADDRREQ;
return ret;
......
......@@ -22,6 +22,7 @@
#include <linux/backing-dev.h>
#include <linux/uuid.h>
#include <linux/mm_types.h>
#include <linux/dns_resolver.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <net/sock.h>
......@@ -77,6 +78,8 @@ struct afs_addr_list {
unsigned char nr_addrs;
unsigned char index; /* Address currently in use */
unsigned char nr_ipv4; /* Number of IPv4 addresses */
enum dns_record_source source:8;
enum dns_lookup_status status:8;
unsigned long probed; /* Mask of servers that have been probed */
unsigned long yfs; /* Mask of servers that are YFS */
struct sockaddr_rxrpc addrs[];
......@@ -355,12 +358,51 @@ struct afs_cell {
rwlock_t proc_lock;
/* VL server list. */
rwlock_t vl_addrs_lock; /* Lock on vl_addrs */
struct afs_addr_list __rcu *vl_addrs; /* List of VL servers */
rwlock_t vl_servers_lock; /* Lock on vl_servers */
struct afs_vlserver_list __rcu *vl_servers;
u8 name_len; /* Length of name */
char name[64 + 1]; /* Cell name, case-flattened and NUL-padded */
};
/*
* Volume Location server record.
*/
struct afs_vlserver {
struct rcu_head rcu;
struct afs_addr_list __rcu *addresses; /* List of addresses for this VL server */
unsigned long flags;
#define AFS_VLSERVER_FL_PROBED 0 /* The VL server has been probed */
#define AFS_VLSERVER_FL_PROBING 1 /* VL server is being probed */
rwlock_t lock; /* Lock on addresses */
atomic_t usage;
u16 name_len; /* Length of name */
u16 port;
char name[]; /* Server name, case-flattened */
};
/*
* Weighted list of Volume Location servers.
*/
struct afs_vlserver_entry {
u16 priority; /* Preference (as SRV) */
u16 weight; /* Weight (as SRV) */
enum dns_record_source source:8;
enum dns_lookup_status status:8;
struct afs_vlserver *server;
};
struct afs_vlserver_list {
struct rcu_head rcu;
atomic_t usage;
u8 nr_servers;
u8 index; /* Server currently in use */
enum dns_record_source source:8;
enum dns_lookup_status status:8;
rwlock_t lock;
struct afs_vlserver_entry servers[];
};
/*
* Cached VLDB entry.
*
......@@ -616,6 +658,23 @@ struct afs_addr_cursor {
bool responded; /* T if the current address responded */
};
/*
* Cursor for iterating over a set of volume location servers.
*/
struct afs_vl_cursor {
struct afs_addr_cursor ac;
struct afs_cell *cell; /* The cell we're querying */
struct afs_vlserver_list *server_list; /* Current server list (pins ref) */
struct key *key; /* Key for the server */
unsigned char start; /* Initial index in server list */
unsigned char index; /* Number of servers tried beyond start */
short error;
unsigned short flags;
#define AFS_VL_CURSOR_STOP 0x0001 /* Set to cease iteration */
#define AFS_VL_CURSOR_RETRY 0x0002 /* Set to do a retry */
#define AFS_VL_CURSOR_RETRIED 0x0004 /* Set if started a retry */
};
/*
* Cursor for iterating over a set of fileservers.
*/
......@@ -662,12 +721,12 @@ extern struct afs_addr_list *afs_alloc_addrlist(unsigned int,
unsigned short,
unsigned short);
extern void afs_put_addrlist(struct afs_addr_list *);
extern struct afs_addr_list *afs_parse_text_addrs(const char *, size_t, char,
extern struct afs_vlserver_list *afs_parse_text_addrs(struct afs_net *,
const char *, size_t, char,
unsigned short, unsigned short);
extern struct afs_addr_list *afs_dns_query(struct afs_cell *, time64_t *);
extern struct afs_vlserver_list *afs_dns_query(struct afs_cell *, time64_t *);
extern bool afs_iterate_addresses(struct afs_addr_cursor *);
extern int afs_end_cursor(struct afs_addr_cursor *);
extern int afs_set_vl_cursor(struct afs_addr_cursor *, struct afs_cell *);
extern void afs_merge_fs_addr4(struct afs_addr_list *, __be32, u16);
extern void afs_merge_fs_addr6(struct afs_addr_list *, __be32 *, u16);
......@@ -1088,14 +1147,43 @@ extern void afs_fs_exit(void);
/*
* vlclient.c
*/
extern struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_net *,
struct afs_addr_cursor *,
struct key *, const char *, int);
extern struct afs_addr_list *afs_vl_get_addrs_u(struct afs_net *, struct afs_addr_cursor *,
struct key *, const uuid_t *);
extern struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_vl_cursor *,
const char *, int);
extern struct afs_addr_list *afs_vl_get_addrs_u(struct afs_vl_cursor *, const uuid_t *);
extern int afs_vl_get_capabilities(struct afs_net *, struct afs_addr_cursor *, struct key *);
extern struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_net *, struct afs_addr_cursor *,
struct key *, const uuid_t *);
extern struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_vl_cursor *, const uuid_t *);
/*
* vl_rotate.c
*/
extern bool afs_begin_vlserver_operation(struct afs_vl_cursor *,
struct afs_cell *, struct key *);
extern bool afs_select_vlserver(struct afs_vl_cursor *);
extern bool afs_select_current_vlserver(struct afs_vl_cursor *);
extern int afs_end_vlserver_operation(struct afs_vl_cursor *);
/*
* vlserver_list.c
*/
static inline struct afs_vlserver *afs_get_vlserver(struct afs_vlserver *vlserver)
{
atomic_inc(&vlserver->usage);
return vlserver;
}
static inline struct afs_vlserver_list *afs_get_vlserverlist(struct afs_vlserver_list *vllist)
{
if (vllist)
atomic_inc(&vllist->usage);
return vllist;
}
extern struct afs_vlserver *afs_alloc_vlserver(const char *, size_t, unsigned short);
extern void afs_put_vlserver(struct afs_net *, struct afs_vlserver *);
extern struct afs_vlserver_list *afs_alloc_vlserver_list(unsigned int);
extern void afs_put_vlserverlist(struct afs_net *, struct afs_vlserver_list *);
extern struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *,
const void *, size_t);
/*
* volume.c
......
......@@ -17,6 +17,11 @@
#include <linux/uaccess.h>
#include "internal.h"
struct afs_vl_seq_net_private {
struct seq_net_private seq; /* Must be first */
struct afs_vlserver_list *vllist;
};
static inline struct afs_net *afs_seq2net(struct seq_file *m)
{
return afs_net(seq_file_net(m));
......@@ -247,61 +252,102 @@ static const struct seq_operations afs_proc_cell_volumes_ops = {
.show = afs_proc_cell_volumes_show,
};
static const char *const dns_record_sources[NR__dns_record_source + 1] = {
[DNS_RECORD_UNAVAILABLE] = "unav",
[DNS_RECORD_FROM_CONFIG] = "cfg",
[DNS_RECORD_FROM_DNS_A] = "A",
[DNS_RECORD_FROM_DNS_AFSDB] = "AFSDB",
[DNS_RECORD_FROM_DNS_SRV] = "SRV",
[DNS_RECORD_FROM_NSS] = "nss",
[NR__dns_record_source] = "[weird]"
};
static const char *const dns_lookup_statuses[NR__dns_lookup_status + 1] = {
[DNS_LOOKUP_NOT_DONE] = "no-lookup",
[DNS_LOOKUP_GOOD] = "good",
[DNS_LOOKUP_GOOD_WITH_BAD] = "good/bad",
[DNS_LOOKUP_BAD] = "bad",
[DNS_LOOKUP_GOT_NOT_FOUND] = "not-found",
[DNS_LOOKUP_GOT_LOCAL_FAILURE] = "local-failure",
[DNS_LOOKUP_GOT_TEMP_FAILURE] = "temp-failure",
[DNS_LOOKUP_GOT_NS_FAILURE] = "ns-failure",
[NR__dns_lookup_status] = "[weird]"
};
/*
* Display the list of Volume Location servers we're using for a cell.
*/
static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v)
{
struct sockaddr_rxrpc *addr = v;
const struct afs_vl_seq_net_private *priv = m->private;
const struct afs_vlserver_list *vllist = priv->vllist;
const struct afs_vlserver_entry *entry;
const struct afs_vlserver *vlserver;
const struct afs_addr_list *alist;
int i;
/* display header on line 1 */
if (v == (void *)1) {
seq_puts(m, "ADDRESS\n");
if (v == SEQ_START_TOKEN) {
seq_printf(m, "# source %s, status %s\n",
dns_record_sources[vllist->source],
dns_lookup_statuses[vllist->status]);
return 0;
}
/* display one cell per line on subsequent lines */
seq_printf(m, "%pISp\n", &addr->transport);
entry = v;
vlserver = entry->server;
alist = rcu_dereference(vlserver->addresses);
seq_printf(m, "%s [p=%hu w=%hu s=%s,%s]:\n",
vlserver->name, entry->priority, entry->weight,
dns_record_sources[alist ? alist->source : entry->source],
dns_lookup_statuses[alist ? alist->status : entry->status]);
if (alist) {
for (i = 0; i < alist->nr_addrs; i++)
seq_printf(m, " %c %pISpc\n",
alist->index == i ? '>' : '-',
&alist->addrs[i].transport);
}
return 0;
}
static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
__acquires(rcu)
{
struct afs_addr_list *alist;
struct afs_vl_seq_net_private *priv = m->private;
struct afs_vlserver_list *vllist;
struct afs_cell *cell = PDE_DATA(file_inode(m->file));
loff_t pos = *_pos;
rcu_read_lock();
alist = rcu_dereference(cell->vl_addrs);
vllist = rcu_dereference(cell->vl_servers);
priv->vllist = vllist;
/* allow for the header line */
if (!pos)
return (void *) 1;
pos--;
if (pos < 0)
*_pos = pos = 0;
if (pos == 0)
return SEQ_START_TOKEN;
if (!alist || pos >= alist->nr_addrs)
if (!vllist || pos - 1 >= vllist->nr_servers)
return NULL;
return alist->addrs + pos;
return &vllist->servers[pos - 1];
}
static void *afs_proc_cell_vlservers_next(struct seq_file *m, void *v,
loff_t *_pos)
{
struct afs_addr_list *alist;
struct afs_cell *cell = PDE_DATA(file_inode(m->file));
struct afs_vl_seq_net_private *priv = m->private;
struct afs_vlserver_list *vllist = priv->vllist;
loff_t pos;
alist = rcu_dereference(cell->vl_addrs);
pos = *_pos;
(*_pos)++;
if (!alist || pos >= alist->nr_addrs)
pos++;
*_pos = pos;
if (!vllist || pos - 1 >= vllist->nr_servers)
return NULL;
return alist->addrs + pos;
return &vllist->servers[pos - 1];
}
static void afs_proc_cell_vlservers_stop(struct seq_file *m, void *v)
......@@ -562,7 +608,7 @@ int afs_proc_cell_setup(struct afs_cell *cell)
if (!proc_create_net_data("vlservers", 0444, dir,
&afs_proc_cell_vlservers_ops,
sizeof(struct seq_net_private),
sizeof(struct afs_vl_seq_net_private),
cell) ||
!proc_create_net_data("volumes", 0444, dir,
&afs_proc_cell_volumes_ops,
......
......@@ -246,41 +246,23 @@ static struct afs_server *afs_alloc_server(struct afs_net *net,
static struct afs_addr_list *afs_vl_lookup_addrs(struct afs_cell *cell,
struct key *key, const uuid_t *uuid)
{
struct afs_addr_cursor ac;
struct afs_addr_list *alist;
struct afs_vl_cursor vc;
struct afs_addr_list *alist = NULL;
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->yfs))
alist = afs_yfsvl_get_endpoints(cell->net, &ac, key, uuid);
ret = -ERESTARTSYS;
if (afs_begin_vlserver_operation(&vc, cell, key)) {
while (afs_select_vlserver(&vc)) {
if (test_bit(vc.ac.index, &vc.ac.alist->yfs))
alist = afs_yfsvl_get_endpoints(&vc, uuid);
else
alist = afs_vl_get_addrs_u(cell->net, &ac, key, uuid);
switch (ac.error) {
case 0:
afs_end_cursor(&ac);
return alist;
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;
alist = afs_vl_get_addrs_u(&vc, uuid);
}
ret = afs_end_vlserver_operation(&vc);
}
error:
return ERR_PTR(afs_end_cursor(&ac));
return ret < 0 ? ERR_PTR(ret) : alist;
}
/*
......
/* AFS vlserver list management.
*
* Copyright (C) 2018 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"
struct afs_vlserver *afs_alloc_vlserver(const char *name, size_t name_len,
unsigned short port)
{
struct afs_vlserver *vlserver;
vlserver = kzalloc(struct_size(vlserver, name, name_len + 1),
GFP_KERNEL);
if (vlserver) {
atomic_set(&vlserver->usage, 1);
rwlock_init(&vlserver->lock);
vlserver->name_len = name_len;
vlserver->port = port;
memcpy(vlserver->name, name, name_len);
}
return vlserver;
}
static void afs_vlserver_rcu(struct rcu_head *rcu)
{
struct afs_vlserver *vlserver = container_of(rcu, struct afs_vlserver, rcu);
afs_put_addrlist(rcu_access_pointer(vlserver->addresses));
kfree_rcu(vlserver, rcu);
}
void afs_put_vlserver(struct afs_net *net, struct afs_vlserver *vlserver)
{
if (vlserver) {
unsigned int u = atomic_dec_return(&vlserver->usage);
//_debug("VL PUT %p{%u}", vlserver, u);
if (u == 0)
call_rcu(&vlserver->rcu, afs_vlserver_rcu);
}
}
struct afs_vlserver_list *afs_alloc_vlserver_list(unsigned int nr_servers)
{
struct afs_vlserver_list *vllist;
vllist = kzalloc(struct_size(vllist, servers, nr_servers), GFP_KERNEL);
if (vllist) {
atomic_set(&vllist->usage, 1);
rwlock_init(&vllist->lock);
}
return vllist;
}
void afs_put_vlserverlist(struct afs_net *net, struct afs_vlserver_list *vllist)
{
if (vllist) {
unsigned int u = atomic_dec_return(&vllist->usage);
//_debug("VLLS PUT %p{%u}", vllist, u);
if (u == 0) {
int i;
for (i = 0; i < vllist->nr_servers; i++) {
afs_put_vlserver(net, vllist->servers[i].server);
}
kfree_rcu(vllist, rcu);
}
}
}
static u16 afs_extract_le16(const u8 **_b)
{
u16 val;
val = (u16)*(*_b)++ << 0;
val |= (u16)*(*_b)++ << 8;
return val;
}
/*
* Build a VL server address list from a DNS queried server list.
*/
static struct afs_addr_list *afs_extract_vl_addrs(const u8 **_b, const u8 *end,
u8 nr_addrs, u16 port)
{
struct afs_addr_list *alist;
const u8 *b = *_b;
int ret = -EINVAL;
alist = afs_alloc_addrlist(nr_addrs, VL_SERVICE, port);
if (!alist)
return ERR_PTR(-ENOMEM);
if (nr_addrs == 0)
return alist;
for (; nr_addrs > 0 && end - b >= nr_addrs; nr_addrs--) {
struct dns_server_list_v1_address hdr;
__be32 x[4];
hdr.address_type = *b++;
switch (hdr.address_type) {
case DNS_ADDRESS_IS_IPV4:
if (end - b < 4) {
_leave(" = -EINVAL [short inet]");
goto error;
}
memcpy(x, b, 4);
afs_merge_fs_addr4(alist, x[0], port);
b += 4;
break;
case DNS_ADDRESS_IS_IPV6:
if (end - b < 16) {
_leave(" = -EINVAL [short inet6]");
goto error;
}
memcpy(x, b, 16);
afs_merge_fs_addr6(alist, x, port);
b += 16;
break;
default:
_leave(" = -EADDRNOTAVAIL [unknown af %u]",
hdr.address_type);
ret = -EADDRNOTAVAIL;
goto error;
}
}
/* Start with IPv6 if available. */
if (alist->nr_ipv4 < alist->nr_addrs)
alist->index = alist->nr_ipv4;
*_b = b;
return alist;
error:
*_b = b;
afs_put_addrlist(alist);
return ERR_PTR(ret);
}
/*
* Build a VL server list from a DNS queried server list.
*/
struct afs_vlserver_list *afs_extract_vlserver_list(struct afs_cell *cell,
const void *buffer,
size_t buffer_size)
{
const struct dns_server_list_v1_header *hdr = buffer;
struct dns_server_list_v1_server bs;
struct afs_vlserver_list *vllist, *previous;
struct afs_addr_list *addrs;
struct afs_vlserver *server;
const u8 *b = buffer, *end = buffer + buffer_size;
int ret = -ENOMEM, nr_servers, i, j;
_enter("");
/* Check that it's a server list, v1 */
if (end - b < sizeof(*hdr) ||
hdr->hdr.content != DNS_PAYLOAD_IS_SERVER_LIST ||
hdr->hdr.version != 1) {
pr_notice("kAFS: Got DNS record [%u,%u] len %zu\n",
hdr->hdr.content, hdr->hdr.version, end - b);
ret = -EDESTADDRREQ;
goto dump;
}
nr_servers = hdr->nr_servers;
vllist = afs_alloc_vlserver_list(nr_servers);
if (!vllist)
return ERR_PTR(-ENOMEM);
vllist->source = (hdr->source < NR__dns_record_source) ?
hdr->source : NR__dns_record_source;
vllist->status = (hdr->status < NR__dns_lookup_status) ?
hdr->status : NR__dns_lookup_status;
read_lock(&cell->vl_servers_lock);
previous = afs_get_vlserverlist(
rcu_dereference_protected(cell->vl_servers,
lockdep_is_held(&cell->vl_servers_lock)));
read_unlock(&cell->vl_servers_lock);
b += sizeof(*hdr);
while (end - b >= sizeof(bs)) {
bs.name_len = afs_extract_le16(&b);
bs.priority = afs_extract_le16(&b);
bs.weight = afs_extract_le16(&b);
bs.port = afs_extract_le16(&b);
bs.source = *b++;
bs.status = *b++;
bs.protocol = *b++;
bs.nr_addrs = *b++;
_debug("extract %u %u %u %u %u %u %*.*s",
bs.name_len, bs.priority, bs.weight,
bs.port, bs.protocol, bs.nr_addrs,
bs.name_len, bs.name_len, b);
if (end - b < bs.name_len)
break;
ret = -EPROTONOSUPPORT;
if (bs.protocol == DNS_SERVER_PROTOCOL_UNSPECIFIED) {
bs.protocol = DNS_SERVER_PROTOCOL_UDP;
} else if (bs.protocol != DNS_SERVER_PROTOCOL_UDP) {
_leave(" = [proto %u]", bs.protocol);
goto error;
}
if (bs.port == 0)
bs.port = AFS_VL_PORT;
if (bs.source > NR__dns_record_source)
bs.source = NR__dns_record_source;
if (bs.status > NR__dns_lookup_status)
bs.status = NR__dns_lookup_status;
server = NULL;
if (previous) {
/* See if we can update an old server record */
for (i = 0; i < previous->nr_servers; i++) {
struct afs_vlserver *p = previous->servers[i].server;
if (p->name_len == bs.name_len &&
p->port == bs.port &&
strncasecmp(b, p->name, bs.name_len) == 0) {
server = afs_get_vlserver(p);
break;
}
}
}
if (!server) {
ret = -ENOMEM;
server = afs_alloc_vlserver(b, bs.name_len, bs.port);
if (!server)
goto error;
}
b += bs.name_len;
/* Extract the addresses - note that we can't skip this as we
* have to advance the payload pointer.
*/
addrs = afs_extract_vl_addrs(&b, end, bs.nr_addrs, bs.port);
if (IS_ERR(addrs)) {
ret = PTR_ERR(addrs);
goto error_2;
}
if (vllist->nr_servers >= nr_servers) {
_debug("skip %u >= %u", vllist->nr_servers, nr_servers);
afs_put_addrlist(addrs);
afs_put_vlserver(cell->net, server);
continue;
}
addrs->source = bs.source;
addrs->status = bs.status;
if (addrs->nr_addrs == 0) {
afs_put_addrlist(addrs);
if (!rcu_access_pointer(server->addresses)) {
afs_put_vlserver(cell->net, server);
continue;
}
} else {
struct afs_addr_list *old = addrs;
write_lock(&server->lock);
rcu_swap_protected(server->addresses, old,
lockdep_is_held(&server->lock));
write_unlock(&server->lock);
afs_put_addrlist(old);
}
/* TODO: Might want to check for duplicates */
/* Insertion-sort by priority and weight */
for (j = 0; j < vllist->nr_servers; j++) {
if (bs.priority < vllist->servers[j].priority)
break; /* Lower preferable */
if (bs.priority == vllist->servers[j].priority &&
bs.weight > vllist->servers[j].weight)
break; /* Higher preferable */
}
if (j < vllist->nr_servers) {
memmove(vllist->servers + j + 1,
vllist->servers + j,
(vllist->nr_servers - j) * sizeof(struct afs_vlserver_entry));
}
vllist->servers[j].priority = bs.priority;
vllist->servers[j].weight = bs.weight;
vllist->servers[j].server = server;
vllist->nr_servers++;
}
if (b != end) {
_debug("parse error %zd", b - end);
goto error;
}
afs_put_vlserverlist(cell->net, previous);
_leave(" = ok [%u]", vllist->nr_servers);
return vllist;
error_2:
afs_put_vlserver(cell->net, server);
error:
afs_put_vlserverlist(cell->net, vllist);
afs_put_vlserverlist(cell->net, previous);
dump:
if (ret != -ENOMEM) {
printk(KERN_DEBUG "DNS: at %zu\n", (const void *)b - buffer);
print_hex_dump_bytes("DNS: ", DUMP_PREFIX_NONE, buffer, buffer_size);
}
return ERR_PTR(ret);
}
/* Handle vlserver selection and rotation.
*
* Copyright (C) 2018 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 Licence
* as published by the Free Software Foundation; either version
* 2 of the Licence, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include "internal.h"
#include "afs_vl.h"
/*
* Begin an operation on a volume location server.
*/
bool afs_begin_vlserver_operation(struct afs_vl_cursor *vc, struct afs_cell *cell,
struct key *key)
{
memset(vc, 0, sizeof(*vc));
vc->cell = cell;
vc->key = key;
vc->error = -EDESTADDRREQ;
vc->ac.error = SHRT_MAX;
if (signal_pending(current)) {
vc->error = -EINTR;
vc->flags |= AFS_VL_CURSOR_STOP;
return false;
}
return true;
}
/*
* Begin iteration through a server list, starting with the last used server if
* possible, or the last recorded good server if not.
*/
static bool afs_start_vl_iteration(struct afs_vl_cursor *vc)
{
struct afs_cell *cell = vc->cell;
if (wait_on_bit(&cell->flags, AFS_CELL_FL_NO_LOOKUP_YET,
TASK_INTERRUPTIBLE)) {
vc->error = -ERESTARTSYS;
return false;
}
read_lock(&cell->vl_servers_lock);
vc->server_list = afs_get_vlserverlist(
rcu_dereference_protected(cell->vl_servers,
lockdep_is_held(&cell->vl_servers_lock)));
read_unlock(&cell->vl_servers_lock);
if (!vc->server_list || !vc->server_list->nr_servers)
return false;
vc->start = READ_ONCE(vc->server_list->index);
vc->index = vc->start;
return true;
}
/*
* Select the vlserver to use. May be called multiple times to rotate
* through the vlservers.
*/
bool afs_select_vlserver(struct afs_vl_cursor *vc)
{
struct afs_addr_list *alist;
struct afs_vlserver *vlserver;
int error = vc->ac.error;
_enter("%u/%u,%u/%u,%d,%d",
vc->index, vc->start,
vc->ac.index, vc->ac.start,
error, vc->ac.abort_code);
if (vc->flags & AFS_VL_CURSOR_STOP) {
_leave(" = f [stopped]");
return false;
}
/* Evaluate the result of the previous operation, if there was one. */
switch (error) {
case SHRT_MAX:
goto start;
default:
case 0:
/* Success or local failure. Stop. */
vc->error = error;
vc->flags |= AFS_VL_CURSOR_STOP;
_leave(" = f [okay/local %d]", vc->ac.error);
return false;
case -ECONNABORTED:
/* The far side rejected the operation on some grounds. This
* might involve the server being busy or the volume having been moved.
*/
switch (vc->ac.abort_code) {
case AFSVL_IO:
case AFSVL_BADVOLOPER:
case AFSVL_NOMEM:
/* The server went weird. */
vc->error = -EREMOTEIO;
//write_lock(&vc->cell->vl_servers_lock);
//vc->server_list->weird_mask |= 1 << vc->index;
//write_unlock(&vc->cell->vl_servers_lock);
goto next_server;
default:
vc->error = afs_abort_to_error(vc->ac.abort_code);
goto failed;
}
case -ENETUNREACH:
case -EHOSTUNREACH:
case -ECONNREFUSED:
case -ETIMEDOUT:
case -ETIME:
_debug("no conn %d", error);
vc->error = error;
goto iterate_address;
case -ECONNRESET:
_debug("call reset");
vc->error = error;
vc->flags |= AFS_VL_CURSOR_RETRY;
goto next_server;
}
restart_from_beginning:
_debug("restart");
afs_end_cursor(&vc->ac);
afs_put_vlserverlist(vc->cell->net, vc->server_list);
vc->server_list = NULL;
if (vc->flags & AFS_VL_CURSOR_RETRIED)
goto failed;
vc->flags |= AFS_VL_CURSOR_RETRIED;
start:
_debug("start");
/* TODO: Consider checking the VL server list */
if (!afs_start_vl_iteration(vc))
goto failed;
use_server:
_debug("use");
/* We're starting on a different vlserver from the list. We need to
* check it, find its address list and probe its capabilities before we
* use it.
*/
ASSERTCMP(vc->ac.alist, ==, NULL);
vlserver = vc->server_list->servers[vc->index].server;
// TODO: Check the vlserver occasionally
//if (!afs_check_vlserver_record(vc, vlserver))
// goto failed;
_debug("USING VLSERVER: %s", vlserver->name);
read_lock(&vlserver->lock);
alist = rcu_dereference_protected(vlserver->addresses,
lockdep_is_held(&vlserver->lock));
afs_get_addrlist(alist);
read_unlock(&vlserver->lock);
memset(&vc->ac, 0, sizeof(vc->ac));
/* Probe the current vlserver if we haven't done so yet. */
#if 0 // TODO
if (!test_bit(AFS_VLSERVER_FL_PROBED, &vlserver->flags)) {
vc->ac.alist = afs_get_addrlist(alist);
if (!afs_probe_vlserver(vc)) {
error = vc->ac.error;
switch (error) {
case -ENOMEM:
case -ERESTARTSYS:
case -EINTR:
goto failed_set_error;
default:
goto next_server;
}
}
}
#endif
if (!vc->ac.alist)
vc->ac.alist = alist;
else
afs_put_addrlist(alist);
vc->ac.start = READ_ONCE(alist->index);
vc->ac.index = vc->ac.start;
iterate_address:
ASSERT(vc->ac.alist);
_debug("iterate %d/%d", vc->ac.index, vc->ac.alist->nr_addrs);
/* Iterate over the current server's address list to try and find an
* address on which it will respond to us.
*/
if (!afs_iterate_addresses(&vc->ac))
goto next_server;
_leave(" = t %pISpc", &vc->ac.addr->transport);
return true;
next_server:
_debug("next");
afs_end_cursor(&vc->ac);
vc->index++;
if (vc->index >= vc->server_list->nr_servers)
vc->index = 0;
if (vc->index != vc->start)
goto use_server;
/* That's all the servers poked to no good effect. Try again if some
* of them were busy.
*/
if (vc->flags & AFS_VL_CURSOR_RETRY)
goto restart_from_beginning;
goto failed;
failed:
vc->flags |= AFS_VL_CURSOR_STOP;
afs_end_cursor(&vc->ac);
_leave(" = f [failed %d]", vc->error);
return false;
}
/*
* Tidy up a volume location server cursor and unlock the vnode.
*/
int afs_end_vlserver_operation(struct afs_vl_cursor *vc)
{
struct afs_net *net = vc->cell->net;
afs_end_cursor(&vc->ac);
afs_put_vlserverlist(net, vc->server_list);
if (vc->error == -ECONNABORTED)
vc->error = afs_abort_to_error(vc->ac.abort_code);
return vc->error;
}
......@@ -128,14 +128,13 @@ static const struct afs_call_type afs_RXVLGetEntryByNameU = {
* Dispatch a get volume entry by name or ID operation (uuid variant). If the
* volname is a decimal number then it's a volume ID not a volume name.
*/
struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_net *net,
struct afs_addr_cursor *ac,
struct key *key,
struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_vl_cursor *vc,
const char *volname,
int volnamesz)
{
struct afs_vldb_entry *entry;
struct afs_call *call;
struct afs_net *net = vc->cell->net;
size_t reqsz, padsz;
__be32 *bp;
......@@ -155,7 +154,7 @@ struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_net *net,
return ERR_PTR(-ENOMEM);
}
call->key = key;
call->key = vc->key;
call->reply[0] = entry;
call->ret_reply0 = true;
......@@ -168,7 +167,7 @@ struct afs_vldb_entry *afs_vl_get_entry_by_name_u(struct afs_net *net,
memset((void *)bp + volnamesz, 0, padsz);
trace_afs_make_vl_call(call);
return (struct afs_vldb_entry *)afs_make_call(ac, call, GFP_KERNEL, false);
return (struct afs_vldb_entry *)afs_make_call(&vc->ac, call, GFP_KERNEL, false);
}
/*
......@@ -266,14 +265,13 @@ static const struct afs_call_type afs_RXVLGetAddrsU = {
* Dispatch an operation to get the addresses for a server, where the server is
* nominated by UUID.
*/
struct afs_addr_list *afs_vl_get_addrs_u(struct afs_net *net,
struct afs_addr_cursor *ac,
struct key *key,
struct afs_addr_list *afs_vl_get_addrs_u(struct afs_vl_cursor *vc,
const uuid_t *uuid)
{
struct afs_ListAddrByAttributes__xdr *r;
const struct afs_uuid *u = (const struct afs_uuid *)uuid;
struct afs_call *call;
struct afs_net *net = vc->cell->net;
__be32 *bp;
int i;
......@@ -285,7 +283,7 @@ struct afs_addr_list *afs_vl_get_addrs_u(struct afs_net *net,
if (!call)
return ERR_PTR(-ENOMEM);
call->key = key;
call->key = vc->key;
call->reply[0] = NULL;
call->ret_reply0 = true;
......@@ -306,7 +304,7 @@ struct afs_addr_list *afs_vl_get_addrs_u(struct afs_net *net,
r->uuid.node[i] = htonl(u->node[i]);
trace_afs_make_vl_call(call);
return (struct afs_addr_list *)afs_make_call(ac, call, GFP_KERNEL, false);
return (struct afs_addr_list *)afs_make_call(&vc->ac, call, GFP_KERNEL, false);
}
/*
......@@ -367,14 +365,13 @@ static const struct afs_call_type afs_RXVLGetCapabilities = {
};
/*
* Probe a fileserver for the capabilities that it supports. This can
* Probe a volume server for the capabilities that it supports. This can
* return up to 196 words.
*
* We use this to probe for service upgrade to determine what the server at the
* other end supports.
*/
int afs_vl_get_capabilities(struct afs_net *net,
struct afs_addr_cursor *ac,
int afs_vl_get_capabilities(struct afs_net *net, struct afs_addr_cursor *ac,
struct key *key)
{
struct afs_call *call;
......@@ -617,12 +614,11 @@ static const struct afs_call_type afs_YFSVLGetEndpoints = {
* Dispatch an operation to get the addresses for a server, where the server is
* nominated by UUID.
*/
struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_net *net,
struct afs_addr_cursor *ac,
struct key *key,
struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_vl_cursor *vc,
const uuid_t *uuid)
{
struct afs_call *call;
struct afs_net *net = vc->cell->net;
__be32 *bp;
_enter("");
......@@ -633,7 +629,7 @@ struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_net *net,
if (!call)
return ERR_PTR(-ENOMEM);
call->key = key;
call->key = vc->key;
call->reply[0] = NULL;
call->ret_reply0 = true;
......@@ -644,5 +640,5 @@ struct afs_addr_list *afs_yfsvl_get_endpoints(struct afs_net *net,
memcpy(bp, uuid, sizeof(*uuid)); /* Type opr_uuid */
trace_afs_make_vl_call(call);
return (struct afs_addr_list *)afs_make_call(ac, call, GFP_KERNEL, false);
return (struct afs_addr_list *)afs_make_call(&vc->ac, call, GFP_KERNEL, false);
}
......@@ -74,55 +74,35 @@ static struct afs_vldb_entry *afs_vl_lookup_vldb(struct afs_cell *cell,
const char *volname,
size_t volnamesz)
{
struct afs_addr_cursor ac;
struct afs_vldb_entry *vldb;
struct afs_vldb_entry *vldb = ERR_PTR(-EDESTADDRREQ);
struct afs_vl_cursor vc;
int ret;
ret = afs_set_vl_cursor(&ac, cell);
if (ret < 0)
return ERR_PTR(ret);
if (!afs_begin_vlserver_operation(&vc, cell, key))
return ERR_PTR(-ERESTARTSYS);
while (afs_iterate_addresses(&ac)) {
if (!test_bit(ac.index, &ac.alist->probed)) {
ret = afs_vl_get_capabilities(cell->net, &ac, key);
while (afs_select_vlserver(&vc)) {
if (!test_bit(vc.ac.index, &vc.ac.alist->probed)) {
ret = afs_vl_get_capabilities(cell->net, &vc.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;
clear_bit(vc.ac.index, &vc.ac.alist->yfs);
set_bit(vc.ac.index, &vc.ac.alist->probed);
vc.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;
set_bit(vc.ac.index, &vc.ac.alist->yfs);
set_bit(vc.ac.index, &vc.ac.alist->probed);
vc.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;
}
vldb = afs_vl_get_entry_by_name_u(&vc, volname, volnamesz);
}
error:
return ERR_PTR(afs_end_cursor(&ac));
ret = afs_end_vlserver_operation(&vc);
return ret < 0 ? ERR_PTR(ret) : vldb;
}
/*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment