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
266 lines
7.2 KiB
C
266 lines
7.2 KiB
C
/*
|
|
* Copyright (c) 2002, 2007 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* This software may be freely redistributed under the terms of the
|
|
* GNU General Public License.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
* Authors: David Woodhouse <dwmw2@infradead.org>
|
|
* David Howells <dhowells@redhat.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/circ_buf.h>
|
|
#include <linux/sched.h>
|
|
#include "internal.h"
|
|
|
|
/*
|
|
* Handle invalidation of an mmap'd file. We invalidate all the PTEs referring
|
|
* to the pages in this file's pagecache, forcing the kernel to go through
|
|
* ->fault() or ->page_mkwrite() - at which point we can handle invalidation
|
|
* more fully.
|
|
*/
|
|
void afs_invalidate_mmap_work(struct work_struct *work)
|
|
{
|
|
struct afs_vnode *vnode = container_of(work, struct afs_vnode, cb_work);
|
|
|
|
unmap_mapping_pages(vnode->netfs.inode.i_mapping, 0, 0, false);
|
|
}
|
|
|
|
static void afs_volume_init_callback(struct afs_volume *volume)
|
|
{
|
|
struct afs_vnode *vnode;
|
|
|
|
down_read(&volume->open_mmaps_lock);
|
|
|
|
list_for_each_entry(vnode, &volume->open_mmaps, cb_mmap_link) {
|
|
if (vnode->cb_v_check != atomic_read(&volume->cb_v_break)) {
|
|
atomic64_set(&vnode->cb_expires_at, AFS_NO_CB_PROMISE);
|
|
queue_work(system_unbound_wq, &vnode->cb_work);
|
|
}
|
|
}
|
|
|
|
up_read(&volume->open_mmaps_lock);
|
|
}
|
|
|
|
/*
|
|
* Allow the fileserver to request callback state (re-)initialisation.
|
|
* Unfortunately, UUIDs are not guaranteed unique.
|
|
*/
|
|
void afs_init_callback_state(struct afs_server *server)
|
|
{
|
|
struct afs_server_entry *se;
|
|
|
|
down_read(&server->cell->vs_lock);
|
|
|
|
list_for_each_entry(se, &server->volumes, slink) {
|
|
se->cb_expires_at = AFS_NO_CB_PROMISE;
|
|
se->volume->cb_expires_at = AFS_NO_CB_PROMISE;
|
|
trace_afs_cb_v_break(se->volume->vid, atomic_read(&se->volume->cb_v_break),
|
|
afs_cb_break_for_s_reinit);
|
|
if (!list_empty(&se->volume->open_mmaps))
|
|
afs_volume_init_callback(se->volume);
|
|
}
|
|
|
|
up_read(&server->cell->vs_lock);
|
|
}
|
|
|
|
/*
|
|
* actually break a callback
|
|
*/
|
|
void __afs_break_callback(struct afs_vnode *vnode, enum afs_cb_break_reason reason)
|
|
{
|
|
_enter("");
|
|
|
|
clear_bit(AFS_VNODE_NEW_CONTENT, &vnode->flags);
|
|
if (atomic64_xchg(&vnode->cb_expires_at, AFS_NO_CB_PROMISE) != AFS_NO_CB_PROMISE) {
|
|
vnode->cb_break++;
|
|
vnode->cb_v_check = atomic_read(&vnode->volume->cb_v_break);
|
|
afs_clear_permits(vnode);
|
|
|
|
if (vnode->lock_state == AFS_VNODE_LOCK_WAITING_FOR_CB)
|
|
afs_lock_may_be_available(vnode);
|
|
|
|
if (reason != afs_cb_break_for_deleted &&
|
|
vnode->status.type == AFS_FTYPE_FILE &&
|
|
atomic_read(&vnode->cb_nr_mmap))
|
|
queue_work(system_unbound_wq, &vnode->cb_work);
|
|
|
|
trace_afs_cb_break(&vnode->fid, vnode->cb_break, reason, true);
|
|
} else {
|
|
trace_afs_cb_break(&vnode->fid, vnode->cb_break, reason, false);
|
|
}
|
|
}
|
|
|
|
void afs_break_callback(struct afs_vnode *vnode, enum afs_cb_break_reason reason)
|
|
{
|
|
write_seqlock(&vnode->cb_lock);
|
|
__afs_break_callback(vnode, reason);
|
|
write_sequnlock(&vnode->cb_lock);
|
|
}
|
|
|
|
/*
|
|
* Look up a volume by volume ID under RCU conditions.
|
|
*/
|
|
static struct afs_volume *afs_lookup_volume_rcu(struct afs_cell *cell,
|
|
afs_volid_t vid)
|
|
{
|
|
struct afs_volume *volume = NULL;
|
|
struct rb_node *p;
|
|
int seq = 1;
|
|
|
|
for (;;) {
|
|
/* Unfortunately, rbtree walking doesn't give reliable results
|
|
* under just the RCU read lock, so we have to check for
|
|
* changes.
|
|
*/
|
|
seq++; /* 2 on the 1st/lockless path, otherwise odd */
|
|
read_seqbegin_or_lock(&cell->volume_lock, &seq);
|
|
|
|
p = rcu_dereference_raw(cell->volumes.rb_node);
|
|
while (p) {
|
|
volume = rb_entry(p, struct afs_volume, cell_node);
|
|
|
|
if (volume->vid < vid)
|
|
p = rcu_dereference_raw(p->rb_left);
|
|
else if (volume->vid > vid)
|
|
p = rcu_dereference_raw(p->rb_right);
|
|
else
|
|
break;
|
|
volume = NULL;
|
|
}
|
|
|
|
if (volume && afs_try_get_volume(volume, afs_volume_trace_get_callback))
|
|
break;
|
|
if (!need_seqretry(&cell->volume_lock, seq))
|
|
break;
|
|
seq |= 1; /* Want a lock next time */
|
|
}
|
|
|
|
done_seqretry(&cell->volume_lock, seq);
|
|
return volume;
|
|
}
|
|
|
|
/*
|
|
* Allow the fileserver to break callbacks at the volume-level. This is
|
|
* typically done when, for example, a R/W volume is snapshotted to a R/O
|
|
* volume (the only way to change an R/O volume). It may also, however, happen
|
|
* when a volserver takes control of a volume (offlining it, moving it, etc.).
|
|
*
|
|
* Every file in that volume will need to be reevaluated.
|
|
*/
|
|
static void afs_break_volume_callback(struct afs_server *server,
|
|
struct afs_volume *volume)
|
|
__releases(RCU)
|
|
{
|
|
struct afs_server_list *slist = rcu_dereference(volume->servers);
|
|
unsigned int i, cb_v_break;
|
|
|
|
write_lock(&volume->cb_v_break_lock);
|
|
|
|
for (i = 0; i < slist->nr_servers; i++)
|
|
if (slist->servers[i].server == server)
|
|
slist->servers[i].cb_expires_at = AFS_NO_CB_PROMISE;
|
|
volume->cb_expires_at = AFS_NO_CB_PROMISE;
|
|
|
|
cb_v_break = atomic_inc_return_release(&volume->cb_v_break);
|
|
trace_afs_cb_v_break(volume->vid, cb_v_break, afs_cb_break_for_volume_callback);
|
|
|
|
write_unlock(&volume->cb_v_break_lock);
|
|
rcu_read_unlock();
|
|
|
|
if (!list_empty(&volume->open_mmaps))
|
|
afs_volume_init_callback(volume);
|
|
}
|
|
|
|
/*
|
|
* allow the fileserver to explicitly break one callback
|
|
* - happens when
|
|
* - the backing file is changed
|
|
* - a lock is released
|
|
*/
|
|
static void afs_break_one_callback(struct afs_server *server,
|
|
struct afs_volume *volume,
|
|
struct afs_fid *fid)
|
|
{
|
|
struct super_block *sb;
|
|
struct afs_vnode *vnode;
|
|
struct inode *inode;
|
|
|
|
/* See if we can find a matching inode - even an I_NEW inode needs to
|
|
* be marked as it can have its callback broken before we finish
|
|
* setting up the local inode.
|
|
*/
|
|
sb = rcu_dereference(volume->sb);
|
|
if (!sb)
|
|
return;
|
|
|
|
inode = find_inode_rcu(sb, fid->vnode, afs_ilookup5_test_by_fid, fid);
|
|
if (inode) {
|
|
vnode = AFS_FS_I(inode);
|
|
afs_break_callback(vnode, afs_cb_break_for_callback);
|
|
} else {
|
|
trace_afs_cb_miss(fid, afs_cb_break_for_callback);
|
|
}
|
|
}
|
|
|
|
static void afs_break_some_callbacks(struct afs_server *server,
|
|
struct afs_callback_break *cbb,
|
|
size_t *_count)
|
|
{
|
|
struct afs_callback_break *residue = cbb;
|
|
struct afs_volume *volume;
|
|
afs_volid_t vid = cbb->fid.vid;
|
|
size_t i;
|
|
|
|
rcu_read_lock();
|
|
volume = afs_lookup_volume_rcu(server->cell, vid);
|
|
if (cbb->fid.vnode == 0 && cbb->fid.unique == 0) {
|
|
afs_break_volume_callback(server, volume);
|
|
*_count -= 1;
|
|
if (*_count)
|
|
memmove(cbb, cbb + 1, sizeof(*cbb) * *_count);
|
|
} else {
|
|
/* TODO: Find all matching volumes if we couldn't match the server and
|
|
* break them anyway.
|
|
*/
|
|
|
|
for (i = *_count; i > 0; cbb++, i--) {
|
|
if (cbb->fid.vid == vid) {
|
|
_debug("- Fid { vl=%08llx n=%llu u=%u }",
|
|
cbb->fid.vid,
|
|
cbb->fid.vnode,
|
|
cbb->fid.unique);
|
|
--*_count;
|
|
if (volume)
|
|
afs_break_one_callback(server, volume, &cbb->fid);
|
|
} else {
|
|
*residue++ = *cbb;
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
afs_put_volume(volume, afs_volume_trace_put_callback);
|
|
}
|
|
|
|
/*
|
|
* allow the fileserver to break callback promises
|
|
*/
|
|
void afs_break_callbacks(struct afs_server *server, size_t count,
|
|
struct afs_callback_break *callbacks)
|
|
{
|
|
_enter("%p,%zu,", server, count);
|
|
|
|
ASSERT(server != NULL);
|
|
|
|
while (count > 0)
|
|
afs_break_some_callbacks(server, callbacks, &count);
|
|
}
|