mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 20:51:44 +00:00
453924de62
Overhaul the third party-induced invalidation handling, making use of the previously added volume-level event counters (cb_scrub and cb_ro_snapshot) that are now being parsed out of the VolSync record returned by the fileserver in many of its replies. This allows better handling of RO (and Backup) volumes. Since these are snapshot of a RW volume that are updated atomically simultantanously across all servers that host them, they only require a single callback promise for the entire volume. The currently upstream code assumes that RO volumes operate in the same manner as RW volumes, and that each file has its own individual callback - which means that it does a status fetch for *every* file in a RO volume, whether or not the volume got "released" (volume callback breaks can occur for other reasons too, such as the volumeserver taking ownership of a volume from a fileserver). To this end, make the following changes: (1) Change the meaning of the volume's cb_v_break counter so that it is now a hint that we need to issue a status fetch to work out the state of a volume. cb_v_break is incremented by volume break callbacks and by server initialisation callbacks. (2) Add a second counter, cb_v_check, to the afs_volume struct such that if this differs from cb_v_break, we need to do a check. When the check is complete, cb_v_check is advanced to what cb_v_break was at the start of the status fetch. (3) Move the list of mmap'd vnodes to the volume and trigger removal of PTEs that map to files on a volume break rather than on a server break. (4) When a server reinitialisation callback comes in, use the server-to-volume reverse mapping added in a preceding patch to iterate over all the volumes using that server and clear the volume callback promises for that server and the general volume promise as a whole to trigger reanalysis. (5) Replace the AFS_VNODE_CB_PROMISED flag with an AFS_NO_CB_PROMISE (TIME64_MIN) value in the cb_expires_at field, reducing the number of checks we need to make. (6) Change afs_check_validity() to quickly see if various event counters have been incremented or if the vnode or volume callback promise is due to expire/has expired without making any changes to the state. That is now left to afs_validate() as this may get more complicated in future as we may have to examine server records too. (7) Overhaul afs_validate() so that it does a single status fetch if we need to check the state of either the vnode or the volume - and do so under appropriate locking. The function does the following steps: (A) If the vnode/volume is no longer seen as valid, then we take the vnode validation lock and, if the volume promise has expired, the volume check lock also. The latter prevents redundant checks being made to find out if a new version of the volume got released. (B) If a previous RPC call found that the volsync changed unexpectedly or that a RO volume was updated, then we unmap all PTEs pointing to the file to stop mmap being used for access. (C) If the vnode is still seen to be of uncertain validity, then we perform an FS.FetchStatus RPC op to jointly update the volume status and the vnode status. This assessment is done as part of parsing the reply: If the RO volume creation timestamp advances, cb_ro_snapshot is incremented; if either the creation or update timestamps changes in an unexpected way, the cb_scrub counter is incremented If the Data Version returned doesn't match the copy we have locally, then we ask for the pagecache to be zapped. This takes care of handling RO update. (D) If cb_scrub differs between volume and vnode, the vnode's pagecache is zapped and the vnode's cb_scrub is updated unless the file is marked as having been deleted. Signed-off-by: David Howells <dhowells@redhat.com> cc: Marc Dionne <marc.dionne@auristor.com> cc: linux-afs@lists.infradead.org
772 lines
18 KiB
C
772 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* /proc interface for AFS
|
|
*
|
|
* Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/sched.h>
|
|
#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));
|
|
}
|
|
|
|
static inline struct afs_net *afs_seq2net_single(struct seq_file *m)
|
|
{
|
|
return afs_net(seq_file_single_net(m));
|
|
}
|
|
|
|
/*
|
|
* Display the list of cells known to the namespace.
|
|
*/
|
|
static int afs_proc_cells_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_vlserver_list *vllist;
|
|
struct afs_cell *cell;
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
/* display header on line 1 */
|
|
seq_puts(m, "USE ACT TTL SV ST NAME\n");
|
|
return 0;
|
|
}
|
|
|
|
cell = list_entry(v, struct afs_cell, proc_link);
|
|
vllist = rcu_dereference(cell->vl_servers);
|
|
|
|
/* display one cell per line on subsequent lines */
|
|
seq_printf(m, "%3u %3u %6lld %2u %2u %s\n",
|
|
refcount_read(&cell->ref),
|
|
atomic_read(&cell->active),
|
|
cell->dns_expiry - ktime_get_real_seconds(),
|
|
vllist ? vllist->nr_servers : 0,
|
|
cell->state,
|
|
cell->name);
|
|
return 0;
|
|
}
|
|
|
|
static void *afs_proc_cells_start(struct seq_file *m, loff_t *_pos)
|
|
__acquires(rcu)
|
|
{
|
|
rcu_read_lock();
|
|
return seq_hlist_start_head_rcu(&afs_seq2net(m)->proc_cells, *_pos);
|
|
}
|
|
|
|
static void *afs_proc_cells_next(struct seq_file *m, void *v, loff_t *pos)
|
|
{
|
|
return seq_hlist_next_rcu(v, &afs_seq2net(m)->proc_cells, pos);
|
|
}
|
|
|
|
static void afs_proc_cells_stop(struct seq_file *m, void *v)
|
|
__releases(rcu)
|
|
{
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static const struct seq_operations afs_proc_cells_ops = {
|
|
.start = afs_proc_cells_start,
|
|
.next = afs_proc_cells_next,
|
|
.stop = afs_proc_cells_stop,
|
|
.show = afs_proc_cells_show,
|
|
};
|
|
|
|
/*
|
|
* handle writes to /proc/fs/afs/cells
|
|
* - to add cells: echo "add <cellname> <IP>[:<IP>][:<IP>]"
|
|
*/
|
|
static int afs_proc_cells_write(struct file *file, char *buf, size_t size)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct afs_net *net = afs_seq2net(m);
|
|
char *name, *args;
|
|
int ret;
|
|
|
|
/* trim to first NL */
|
|
name = memchr(buf, '\n', size);
|
|
if (name)
|
|
*name = 0;
|
|
|
|
/* split into command, name and argslist */
|
|
name = strchr(buf, ' ');
|
|
if (!name)
|
|
goto inval;
|
|
do {
|
|
*name++ = 0;
|
|
} while(*name == ' ');
|
|
if (!*name)
|
|
goto inval;
|
|
|
|
args = strchr(name, ' ');
|
|
if (args) {
|
|
do {
|
|
*args++ = 0;
|
|
} while(*args == ' ');
|
|
if (!*args)
|
|
goto inval;
|
|
}
|
|
|
|
/* determine command to perform */
|
|
_debug("cmd=%s name=%s args=%s", buf, name, args);
|
|
|
|
if (strcmp(buf, "add") == 0) {
|
|
struct afs_cell *cell;
|
|
|
|
cell = afs_lookup_cell(net, name, strlen(name), args, true);
|
|
if (IS_ERR(cell)) {
|
|
ret = PTR_ERR(cell);
|
|
goto done;
|
|
}
|
|
|
|
if (test_and_set_bit(AFS_CELL_FL_NO_GC, &cell->flags))
|
|
afs_unuse_cell(net, cell, afs_cell_trace_unuse_no_pin);
|
|
} else {
|
|
goto inval;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
done:
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
|
|
inval:
|
|
ret = -EINVAL;
|
|
printk("kAFS: Invalid Command on /proc/fs/afs/cells file\n");
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Display the list of addr_prefs known to the namespace.
|
|
*/
|
|
static int afs_proc_addr_prefs_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_addr_preference_list *preflist;
|
|
struct afs_addr_preference *pref;
|
|
struct afs_net *net = afs_seq2net_single(m);
|
|
union {
|
|
struct sockaddr_in sin;
|
|
struct sockaddr_in6 sin6;
|
|
} addr;
|
|
unsigned int i;
|
|
char buf[44]; /* Maximum ipv6 + max subnet is 43 */
|
|
|
|
rcu_read_lock();
|
|
preflist = rcu_dereference(net->address_prefs);
|
|
|
|
if (!preflist) {
|
|
seq_puts(m, "NO PREFS\n");
|
|
return 0;
|
|
}
|
|
|
|
seq_printf(m, "PROT SUBNET PRIOR (v=%u n=%u/%u/%u)\n",
|
|
preflist->version, preflist->ipv6_off, preflist->nr, preflist->max_prefs);
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
for (i = 0; i < preflist->nr; i++) {
|
|
pref = &preflist->prefs[i];
|
|
|
|
addr.sin.sin_family = pref->family;
|
|
if (pref->family == AF_INET) {
|
|
memcpy(&addr.sin.sin_addr, &pref->ipv4_addr,
|
|
sizeof(addr.sin.sin_addr));
|
|
snprintf(buf, sizeof(buf), "%pISc/%u", &addr.sin, pref->subnet_mask);
|
|
seq_printf(m, "UDP %-43.43s %5u\n", buf, pref->prio);
|
|
} else {
|
|
memcpy(&addr.sin6.sin6_addr, &pref->ipv6_addr,
|
|
sizeof(addr.sin6.sin6_addr));
|
|
snprintf(buf, sizeof(buf), "%pISc/%u", &addr.sin6, pref->subnet_mask);
|
|
seq_printf(m, "UDP %-43.43s %5u\n", buf, pref->prio);
|
|
}
|
|
}
|
|
|
|
rcu_read_lock();
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Display the name of the current workstation cell.
|
|
*/
|
|
static int afs_proc_rootcell_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_cell *cell;
|
|
struct afs_net *net;
|
|
|
|
net = afs_seq2net_single(m);
|
|
down_read(&net->cells_lock);
|
|
cell = net->ws_cell;
|
|
if (cell)
|
|
seq_printf(m, "%s\n", cell->name);
|
|
up_read(&net->cells_lock);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Set the current workstation cell and optionally supply its list of volume
|
|
* location servers.
|
|
*
|
|
* echo "cell.name:192.168.231.14" >/proc/fs/afs/rootcell
|
|
*/
|
|
static int afs_proc_rootcell_write(struct file *file, char *buf, size_t size)
|
|
{
|
|
struct seq_file *m = file->private_data;
|
|
struct afs_net *net = afs_seq2net_single(m);
|
|
char *s;
|
|
int ret;
|
|
|
|
ret = -EINVAL;
|
|
if (buf[0] == '.')
|
|
goto out;
|
|
if (memchr(buf, '/', size))
|
|
goto out;
|
|
|
|
/* trim to first NL */
|
|
s = memchr(buf, '\n', size);
|
|
if (s)
|
|
*s = 0;
|
|
|
|
/* determine command to perform */
|
|
_debug("rootcell=%s", buf);
|
|
|
|
ret = afs_cell_init(net, buf);
|
|
|
|
out:
|
|
_leave(" = %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
static const char afs_vol_types[3][3] = {
|
|
[AFSVL_RWVOL] = "RW",
|
|
[AFSVL_ROVOL] = "RO",
|
|
[AFSVL_BACKVOL] = "BK",
|
|
};
|
|
|
|
/*
|
|
* Display the list of volumes known to a cell.
|
|
*/
|
|
static int afs_proc_cell_volumes_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_volume *vol = hlist_entry(v, struct afs_volume, proc_link);
|
|
|
|
/* Display header on line 1 */
|
|
if (v == SEQ_START_TOKEN) {
|
|
seq_puts(m, "USE VID TY NAME\n");
|
|
return 0;
|
|
}
|
|
|
|
seq_printf(m, "%3d %08llx %s %s\n",
|
|
refcount_read(&vol->ref), vol->vid,
|
|
afs_vol_types[vol->type],
|
|
vol->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *afs_proc_cell_volumes_start(struct seq_file *m, loff_t *_pos)
|
|
__acquires(cell->proc_lock)
|
|
{
|
|
struct afs_cell *cell = pde_data(file_inode(m->file));
|
|
|
|
rcu_read_lock();
|
|
return seq_hlist_start_head_rcu(&cell->proc_volumes, *_pos);
|
|
}
|
|
|
|
static void *afs_proc_cell_volumes_next(struct seq_file *m, void *v,
|
|
loff_t *_pos)
|
|
{
|
|
struct afs_cell *cell = pde_data(file_inode(m->file));
|
|
|
|
return seq_hlist_next_rcu(v, &cell->proc_volumes, _pos);
|
|
}
|
|
|
|
static void afs_proc_cell_volumes_stop(struct seq_file *m, void *v)
|
|
__releases(cell->proc_lock)
|
|
{
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static const struct seq_operations afs_proc_cell_volumes_ops = {
|
|
.start = afs_proc_cell_volumes_start,
|
|
.next = afs_proc_cell_volumes_next,
|
|
.stop = afs_proc_cell_volumes_stop,
|
|
.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)
|
|
{
|
|
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;
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
seq_printf(m, "# source %s, status %s\n",
|
|
dns_record_sources[vllist ? vllist->source : 0],
|
|
dns_lookup_statuses[vllist ? vllist->status : 0]);
|
|
return 0;
|
|
}
|
|
|
|
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->preferred == i ? '>' : '-',
|
|
rxrpc_kernel_remote_addr(alist->addrs[i].peer));
|
|
}
|
|
seq_printf(m, " info: fl=%lx rtt=%d\n", vlserver->flags, vlserver->rtt);
|
|
seq_printf(m, " probe: fl=%x e=%d ac=%d out=%d\n",
|
|
vlserver->probe.flags, vlserver->probe.error,
|
|
vlserver->probe.abort_code,
|
|
atomic_read(&vlserver->probe_outstanding));
|
|
return 0;
|
|
}
|
|
|
|
static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
|
|
__acquires(rcu)
|
|
{
|
|
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();
|
|
|
|
vllist = rcu_dereference(cell->vl_servers);
|
|
priv->vllist = vllist;
|
|
|
|
if (pos < 0)
|
|
*_pos = pos = 0;
|
|
if (pos == 0)
|
|
return SEQ_START_TOKEN;
|
|
|
|
if (pos - 1 >= vllist->nr_servers)
|
|
return NULL;
|
|
|
|
return &vllist->servers[pos - 1];
|
|
}
|
|
|
|
static void *afs_proc_cell_vlservers_next(struct seq_file *m, void *v,
|
|
loff_t *_pos)
|
|
{
|
|
struct afs_vl_seq_net_private *priv = m->private;
|
|
struct afs_vlserver_list *vllist = priv->vllist;
|
|
loff_t pos;
|
|
|
|
pos = *_pos;
|
|
pos++;
|
|
*_pos = pos;
|
|
if (!vllist || pos - 1 >= vllist->nr_servers)
|
|
return NULL;
|
|
|
|
return &vllist->servers[pos - 1];
|
|
}
|
|
|
|
static void afs_proc_cell_vlservers_stop(struct seq_file *m, void *v)
|
|
__releases(rcu)
|
|
{
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static const struct seq_operations afs_proc_cell_vlservers_ops = {
|
|
.start = afs_proc_cell_vlservers_start,
|
|
.next = afs_proc_cell_vlservers_next,
|
|
.stop = afs_proc_cell_vlservers_stop,
|
|
.show = afs_proc_cell_vlservers_show,
|
|
};
|
|
|
|
/*
|
|
* Display the list of fileservers we're using within a namespace.
|
|
*/
|
|
static int afs_proc_servers_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_endpoint_state *estate;
|
|
struct afs_addr_list *alist;
|
|
struct afs_server *server;
|
|
unsigned long failed;
|
|
int i;
|
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
seq_puts(m, "UUID REF ACT CELL\n");
|
|
return 0;
|
|
}
|
|
|
|
server = list_entry(v, struct afs_server, proc_link);
|
|
estate = rcu_dereference(server->endpoint_state);
|
|
alist = estate->addresses;
|
|
seq_printf(m, "%pU %3d %3d %s\n",
|
|
&server->uuid,
|
|
refcount_read(&server->ref),
|
|
atomic_read(&server->active),
|
|
server->cell->name);
|
|
seq_printf(m, " - info: fl=%lx rtt=%u\n",
|
|
server->flags, server->rtt);
|
|
seq_printf(m, " - probe: last=%d\n",
|
|
(int)(jiffies - server->probed_at) / HZ);
|
|
failed = estate->failed_set;
|
|
seq_printf(m, " - ESTATE pq=%x np=%u rsp=%lx f=%lx\n",
|
|
estate->probe_seq, atomic_read(&estate->nr_probing),
|
|
estate->responsive_set, estate->failed_set);
|
|
seq_printf(m, " - ALIST v=%u ap=%u\n",
|
|
alist->version, alist->addr_pref_version);
|
|
for (i = 0; i < alist->nr_addrs; i++) {
|
|
const struct afs_address *addr = &alist->addrs[i];
|
|
|
|
seq_printf(m, " [%x] %pISpc%s rtt=%d err=%d p=%u\n",
|
|
i, rxrpc_kernel_remote_addr(addr->peer),
|
|
alist->preferred == i ? "*" :
|
|
test_bit(i, &failed) ? "!" : "",
|
|
rxrpc_kernel_get_srtt(addr->peer),
|
|
addr->last_error, addr->prio);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void *afs_proc_servers_start(struct seq_file *m, loff_t *_pos)
|
|
__acquires(rcu)
|
|
{
|
|
rcu_read_lock();
|
|
return seq_hlist_start_head_rcu(&afs_seq2net(m)->fs_proc, *_pos);
|
|
}
|
|
|
|
static void *afs_proc_servers_next(struct seq_file *m, void *v, loff_t *_pos)
|
|
{
|
|
return seq_hlist_next_rcu(v, &afs_seq2net(m)->fs_proc, _pos);
|
|
}
|
|
|
|
static void afs_proc_servers_stop(struct seq_file *m, void *v)
|
|
__releases(rcu)
|
|
{
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static const struct seq_operations afs_proc_servers_ops = {
|
|
.start = afs_proc_servers_start,
|
|
.next = afs_proc_servers_next,
|
|
.stop = afs_proc_servers_stop,
|
|
.show = afs_proc_servers_show,
|
|
};
|
|
|
|
/*
|
|
* Display the list of strings that may be substituted for the @sys pathname
|
|
* macro.
|
|
*/
|
|
static int afs_proc_sysname_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_net *net = afs_seq2net(m);
|
|
struct afs_sysnames *sysnames = net->sysnames;
|
|
unsigned int i = (unsigned long)v - 1;
|
|
|
|
if (i < sysnames->nr)
|
|
seq_printf(m, "%s\n", sysnames->subs[i]);
|
|
return 0;
|
|
}
|
|
|
|
static void *afs_proc_sysname_start(struct seq_file *m, loff_t *pos)
|
|
__acquires(&net->sysnames_lock)
|
|
{
|
|
struct afs_net *net = afs_seq2net(m);
|
|
struct afs_sysnames *names;
|
|
|
|
read_lock(&net->sysnames_lock);
|
|
|
|
names = net->sysnames;
|
|
if (*pos >= names->nr)
|
|
return NULL;
|
|
return (void *)(unsigned long)(*pos + 1);
|
|
}
|
|
|
|
static void *afs_proc_sysname_next(struct seq_file *m, void *v, loff_t *pos)
|
|
{
|
|
struct afs_net *net = afs_seq2net(m);
|
|
struct afs_sysnames *names = net->sysnames;
|
|
|
|
*pos += 1;
|
|
if (*pos >= names->nr)
|
|
return NULL;
|
|
return (void *)(unsigned long)(*pos + 1);
|
|
}
|
|
|
|
static void afs_proc_sysname_stop(struct seq_file *m, void *v)
|
|
__releases(&net->sysnames_lock)
|
|
{
|
|
struct afs_net *net = afs_seq2net(m);
|
|
|
|
read_unlock(&net->sysnames_lock);
|
|
}
|
|
|
|
static const struct seq_operations afs_proc_sysname_ops = {
|
|
.start = afs_proc_sysname_start,
|
|
.next = afs_proc_sysname_next,
|
|
.stop = afs_proc_sysname_stop,
|
|
.show = afs_proc_sysname_show,
|
|
};
|
|
|
|
/*
|
|
* Allow the @sys substitution to be configured.
|
|
*/
|
|
static int afs_proc_sysname_write(struct file *file, char *buf, size_t size)
|
|
{
|
|
struct afs_sysnames *sysnames, *kill;
|
|
struct seq_file *m = file->private_data;
|
|
struct afs_net *net = afs_seq2net(m);
|
|
char *s, *p, *sub;
|
|
int ret, len;
|
|
|
|
sysnames = kzalloc(sizeof(*sysnames), GFP_KERNEL);
|
|
if (!sysnames)
|
|
return -ENOMEM;
|
|
refcount_set(&sysnames->usage, 1);
|
|
kill = sysnames;
|
|
|
|
p = buf;
|
|
while ((s = strsep(&p, " \t\n"))) {
|
|
len = strlen(s);
|
|
if (len == 0)
|
|
continue;
|
|
ret = -ENAMETOOLONG;
|
|
if (len >= AFSNAMEMAX)
|
|
goto error;
|
|
|
|
if (len >= 4 &&
|
|
s[len - 4] == '@' &&
|
|
s[len - 3] == 's' &&
|
|
s[len - 2] == 'y' &&
|
|
s[len - 1] == 's')
|
|
/* Protect against recursion */
|
|
goto invalid;
|
|
|
|
if (s[0] == '.' &&
|
|
(len < 2 || (len == 2 && s[1] == '.')))
|
|
goto invalid;
|
|
|
|
if (memchr(s, '/', len))
|
|
goto invalid;
|
|
|
|
ret = -EFBIG;
|
|
if (sysnames->nr >= AFS_NR_SYSNAME)
|
|
goto out;
|
|
|
|
if (strcmp(s, afs_init_sysname) == 0) {
|
|
sub = (char *)afs_init_sysname;
|
|
} else {
|
|
ret = -ENOMEM;
|
|
sub = kmemdup(s, len + 1, GFP_KERNEL);
|
|
if (!sub)
|
|
goto out;
|
|
}
|
|
|
|
sysnames->subs[sysnames->nr] = sub;
|
|
sysnames->nr++;
|
|
}
|
|
|
|
if (sysnames->nr == 0) {
|
|
sysnames->subs[0] = sysnames->blank;
|
|
sysnames->nr++;
|
|
}
|
|
|
|
write_lock(&net->sysnames_lock);
|
|
kill = net->sysnames;
|
|
net->sysnames = sysnames;
|
|
write_unlock(&net->sysnames_lock);
|
|
ret = 0;
|
|
out:
|
|
afs_put_sysnames(kill);
|
|
return ret;
|
|
|
|
invalid:
|
|
ret = -EINVAL;
|
|
error:
|
|
goto out;
|
|
}
|
|
|
|
void afs_put_sysnames(struct afs_sysnames *sysnames)
|
|
{
|
|
int i;
|
|
|
|
if (sysnames && refcount_dec_and_test(&sysnames->usage)) {
|
|
for (i = 0; i < sysnames->nr; i++)
|
|
if (sysnames->subs[i] != afs_init_sysname &&
|
|
sysnames->subs[i] != sysnames->blank)
|
|
kfree(sysnames->subs[i]);
|
|
kfree(sysnames);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Display general per-net namespace statistics
|
|
*/
|
|
static int afs_proc_stats_show(struct seq_file *m, void *v)
|
|
{
|
|
struct afs_net *net = afs_seq2net_single(m);
|
|
|
|
seq_puts(m, "kAFS statistics\n");
|
|
|
|
seq_printf(m, "dir-mgmt: look=%u reval=%u inval=%u relpg=%u\n",
|
|
atomic_read(&net->n_lookup),
|
|
atomic_read(&net->n_reval),
|
|
atomic_read(&net->n_inval),
|
|
atomic_read(&net->n_relpg));
|
|
|
|
seq_printf(m, "dir-data: rdpg=%u\n",
|
|
atomic_read(&net->n_read_dir));
|
|
|
|
seq_printf(m, "dir-edit: cr=%u rm=%u\n",
|
|
atomic_read(&net->n_dir_cr),
|
|
atomic_read(&net->n_dir_rm));
|
|
|
|
seq_printf(m, "file-rd : n=%u nb=%lu\n",
|
|
atomic_read(&net->n_fetches),
|
|
atomic_long_read(&net->n_fetch_bytes));
|
|
seq_printf(m, "file-wr : n=%u nb=%lu\n",
|
|
atomic_read(&net->n_stores),
|
|
atomic_long_read(&net->n_store_bytes));
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* initialise /proc/fs/afs/<cell>/
|
|
*/
|
|
int afs_proc_cell_setup(struct afs_cell *cell)
|
|
{
|
|
struct proc_dir_entry *dir;
|
|
struct afs_net *net = cell->net;
|
|
|
|
_enter("%p{%s},%p", cell, cell->name, net->proc_afs);
|
|
|
|
dir = proc_net_mkdir(net->net, cell->name, net->proc_afs);
|
|
if (!dir)
|
|
goto error_dir;
|
|
|
|
if (!proc_create_net_data("vlservers", 0444, dir,
|
|
&afs_proc_cell_vlservers_ops,
|
|
sizeof(struct afs_vl_seq_net_private),
|
|
cell) ||
|
|
!proc_create_net_data("volumes", 0444, dir,
|
|
&afs_proc_cell_volumes_ops,
|
|
sizeof(struct seq_net_private),
|
|
cell))
|
|
goto error_tree;
|
|
|
|
_leave(" = 0");
|
|
return 0;
|
|
|
|
error_tree:
|
|
remove_proc_subtree(cell->name, net->proc_afs);
|
|
error_dir:
|
|
_leave(" = -ENOMEM");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* remove /proc/fs/afs/<cell>/
|
|
*/
|
|
void afs_proc_cell_remove(struct afs_cell *cell)
|
|
{
|
|
struct afs_net *net = cell->net;
|
|
|
|
_enter("");
|
|
remove_proc_subtree(cell->name, net->proc_afs);
|
|
_leave("");
|
|
}
|
|
|
|
/*
|
|
* initialise the /proc/fs/afs/ directory
|
|
*/
|
|
int afs_proc_init(struct afs_net *net)
|
|
{
|
|
struct proc_dir_entry *p;
|
|
|
|
_enter("");
|
|
|
|
p = proc_net_mkdir(net->net, "afs", net->net->proc_net);
|
|
if (!p)
|
|
goto error_dir;
|
|
|
|
if (!proc_create_net_data_write("cells", 0644, p,
|
|
&afs_proc_cells_ops,
|
|
afs_proc_cells_write,
|
|
sizeof(struct seq_net_private),
|
|
NULL) ||
|
|
!proc_create_net_single_write("rootcell", 0644, p,
|
|
afs_proc_rootcell_show,
|
|
afs_proc_rootcell_write,
|
|
NULL) ||
|
|
!proc_create_net("servers", 0444, p, &afs_proc_servers_ops,
|
|
sizeof(struct seq_net_private)) ||
|
|
!proc_create_net_single("stats", 0444, p, afs_proc_stats_show, NULL) ||
|
|
!proc_create_net_data_write("sysname", 0644, p,
|
|
&afs_proc_sysname_ops,
|
|
afs_proc_sysname_write,
|
|
sizeof(struct seq_net_private),
|
|
NULL) ||
|
|
!proc_create_net_single_write("addr_prefs", 0644, p,
|
|
afs_proc_addr_prefs_show,
|
|
afs_proc_addr_prefs_write,
|
|
NULL))
|
|
goto error_tree;
|
|
|
|
net->proc_afs = p;
|
|
_leave(" = 0");
|
|
return 0;
|
|
|
|
error_tree:
|
|
proc_remove(p);
|
|
error_dir:
|
|
_leave(" = -ENOMEM");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* clean up the /proc/fs/afs/ directory
|
|
*/
|
|
void afs_proc_cleanup(struct afs_net *net)
|
|
{
|
|
proc_remove(net->proc_afs);
|
|
net->proc_afs = NULL;
|
|
}
|