summaryrefslogtreecommitdiffstats
path: root/fs/afs/callback.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/afs/callback.c')
-rw-r--r--fs/afs/callback.c333
1 files changed, 333 insertions, 0 deletions
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
new file mode 100644
index 000000000..97283b04f
--- /dev/null
+++ b/fs/afs/callback.c
@@ -0,0 +1,333 @@
+/*
+ * 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"
+
+/*
+ * Create volume and callback interests on a server.
+ */
+static struct afs_cb_interest *afs_create_interest(struct afs_server *server,
+ struct afs_vnode *vnode)
+{
+ struct afs_vol_interest *new_vi, *vi;
+ struct afs_cb_interest *new;
+ struct hlist_node **pp;
+
+ new_vi = kzalloc(sizeof(struct afs_vol_interest), GFP_KERNEL);
+ if (!new_vi)
+ return NULL;
+
+ new = kzalloc(sizeof(struct afs_cb_interest), GFP_KERNEL);
+ if (!new) {
+ kfree(new_vi);
+ return NULL;
+ }
+
+ new_vi->usage = 1;
+ new_vi->vid = vnode->volume->vid;
+ INIT_HLIST_NODE(&new_vi->srv_link);
+ INIT_HLIST_HEAD(&new_vi->cb_interests);
+
+ refcount_set(&new->usage, 1);
+ new->sb = vnode->vfs_inode.i_sb;
+ new->vid = vnode->volume->vid;
+ new->server = afs_get_server(server);
+ INIT_HLIST_NODE(&new->cb_vlink);
+
+ write_lock(&server->cb_break_lock);
+
+ for (pp = &server->cb_volumes.first; *pp; pp = &(*pp)->next) {
+ vi = hlist_entry(*pp, struct afs_vol_interest, srv_link);
+ if (vi->vid < new_vi->vid)
+ continue;
+ if (vi->vid > new_vi->vid)
+ break;
+ vi->usage++;
+ goto found_vi;
+ }
+
+ new_vi->srv_link.pprev = pp;
+ new_vi->srv_link.next = *pp;
+ if (*pp)
+ (*pp)->pprev = &new_vi->srv_link.next;
+ *pp = &new_vi->srv_link;
+ vi = new_vi;
+ new_vi = NULL;
+found_vi:
+
+ new->vol_interest = vi;
+ hlist_add_head(&new->cb_vlink, &vi->cb_interests);
+
+ write_unlock(&server->cb_break_lock);
+ kfree(new_vi);
+ return new;
+}
+
+/*
+ * Set up an interest-in-callbacks record for a volume on a server and
+ * register it with the server.
+ * - Called with vnode->io_lock held.
+ */
+int afs_register_server_cb_interest(struct afs_vnode *vnode,
+ struct afs_server_list *slist,
+ unsigned int index)
+{
+ struct afs_server_entry *entry = &slist->servers[index];
+ struct afs_cb_interest *cbi, *vcbi, *new, *old;
+ struct afs_server *server = entry->server;
+
+again:
+ if (vnode->cb_interest &&
+ likely(vnode->cb_interest == entry->cb_interest))
+ return 0;
+
+ read_lock(&slist->lock);
+ cbi = afs_get_cb_interest(entry->cb_interest);
+ read_unlock(&slist->lock);
+
+ vcbi = vnode->cb_interest;
+ if (vcbi) {
+ if (vcbi == cbi) {
+ afs_put_cb_interest(afs_v2net(vnode), cbi);
+ return 0;
+ }
+
+ /* Use a new interest in the server list for the same server
+ * rather than an old one that's still attached to a vnode.
+ */
+ if (cbi && vcbi->server == cbi->server) {
+ write_seqlock(&vnode->cb_lock);
+ old = vnode->cb_interest;
+ vnode->cb_interest = cbi;
+ write_sequnlock(&vnode->cb_lock);
+ afs_put_cb_interest(afs_v2net(vnode), old);
+ return 0;
+ }
+
+ /* Re-use the one attached to the vnode. */
+ if (!cbi && vcbi->server == server) {
+ write_lock(&slist->lock);
+ if (entry->cb_interest) {
+ write_unlock(&slist->lock);
+ afs_put_cb_interest(afs_v2net(vnode), cbi);
+ goto again;
+ }
+
+ entry->cb_interest = cbi;
+ write_unlock(&slist->lock);
+ return 0;
+ }
+ }
+
+ if (!cbi) {
+ new = afs_create_interest(server, vnode);
+ if (!new)
+ return -ENOMEM;
+
+ write_lock(&slist->lock);
+ if (!entry->cb_interest) {
+ entry->cb_interest = afs_get_cb_interest(new);
+ cbi = new;
+ new = NULL;
+ } else {
+ cbi = afs_get_cb_interest(entry->cb_interest);
+ }
+ write_unlock(&slist->lock);
+ afs_put_cb_interest(afs_v2net(vnode), new);
+ }
+
+ ASSERT(cbi);
+
+ /* Change the server the vnode is using. This entails scrubbing any
+ * interest the vnode had in the previous server it was using.
+ */
+ write_seqlock(&vnode->cb_lock);
+
+ old = vnode->cb_interest;
+ vnode->cb_interest = cbi;
+ vnode->cb_s_break = cbi->server->cb_s_break;
+ vnode->cb_v_break = vnode->volume->cb_v_break;
+ clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags);
+
+ write_sequnlock(&vnode->cb_lock);
+ afs_put_cb_interest(afs_v2net(vnode), old);
+ return 0;
+}
+
+/*
+ * Remove an interest on a server.
+ */
+void afs_put_cb_interest(struct afs_net *net, struct afs_cb_interest *cbi)
+{
+ struct afs_vol_interest *vi;
+
+ if (cbi && refcount_dec_and_test(&cbi->usage)) {
+ if (!hlist_unhashed(&cbi->cb_vlink)) {
+ write_lock(&cbi->server->cb_break_lock);
+
+ hlist_del_init(&cbi->cb_vlink);
+ vi = cbi->vol_interest;
+ cbi->vol_interest = NULL;
+ if (--vi->usage == 0)
+ hlist_del(&vi->srv_link);
+ else
+ vi = NULL;
+
+ write_unlock(&cbi->server->cb_break_lock);
+ kfree(vi);
+ afs_put_server(net, cbi->server);
+ }
+ kfree(cbi);
+ }
+}
+
+/*
+ * allow the fileserver to request callback state (re-)initialisation
+ */
+void afs_init_callback_state(struct afs_server *server)
+{
+ if (!test_and_clear_bit(AFS_SERVER_FL_NEW, &server->flags))
+ server->cb_s_break++;
+}
+
+/*
+ * actually break a callback
+ */
+void afs_break_callback(struct afs_vnode *vnode)
+{
+ _enter("");
+
+ write_seqlock(&vnode->cb_lock);
+
+ clear_bit(AFS_VNODE_NEW_CONTENT, &vnode->flags);
+ if (test_and_clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags)) {
+ vnode->cb_break++;
+ afs_clear_permits(vnode);
+
+ if (vnode->lock_state == AFS_VNODE_LOCK_WAITING_FOR_CB)
+ afs_lock_may_be_available(vnode);
+ }
+
+ write_sequnlock(&vnode->cb_lock);
+}
+
+/*
+ * 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_fid *fid)
+{
+ struct afs_vol_interest *vi;
+ struct afs_cb_interest *cbi;
+ struct afs_iget_data data;
+ struct afs_vnode *vnode;
+ struct inode *inode;
+
+ read_lock(&server->cb_break_lock);
+ hlist_for_each_entry(vi, &server->cb_volumes, srv_link) {
+ if (vi->vid < fid->vid)
+ continue;
+ if (vi->vid > fid->vid) {
+ vi = NULL;
+ break;
+ }
+ //atomic_inc(&vi->usage);
+ break;
+ }
+
+ /* TODO: Find all matching volumes if we couldn't match the server and
+ * break them anyway.
+ */
+ if (!vi)
+ goto out;
+
+ /* Step through all interested superblocks. There may be more than one
+ * because of cell aliasing.
+ */
+ hlist_for_each_entry(cbi, &vi->cb_interests, cb_vlink) {
+ if (fid->vnode == 0 && fid->unique == 0) {
+ /* The callback break applies to an entire volume. */
+ struct afs_super_info *as = AFS_FS_S(cbi->sb);
+ struct afs_volume *volume = as->volume;
+
+ write_lock(&volume->cb_v_break_lock);
+ volume->cb_v_break++;
+ write_unlock(&volume->cb_v_break_lock);
+ } else {
+ data.volume = NULL;
+ data.fid = *fid;
+ inode = ilookup5_nowait(cbi->sb, fid->vnode,
+ afs_iget5_test, &data);
+ if (inode) {
+ vnode = AFS_FS_I(inode);
+ afs_break_callback(vnode);
+ iput(inode);
+ }
+ }
+ }
+
+out:
+ read_unlock(&server->cb_break_lock);
+}
+
+/*
+ * 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);
+ ASSERTCMP(count, <=, AFSCBMAX);
+
+ /* TODO: Sort the callback break list by volume ID */
+
+ for (; count > 0; callbacks++, count--) {
+ _debug("- Fid { vl=%08x n=%u u=%u } CB { v=%u x=%u t=%u }",
+ callbacks->fid.vid,
+ callbacks->fid.vnode,
+ callbacks->fid.unique,
+ callbacks->cb.version,
+ callbacks->cb.expiry,
+ callbacks->cb.type
+ );
+ afs_break_one_callback(server, &callbacks->fid);
+ }
+
+ _leave("");
+ return;
+}
+
+/*
+ * Clear the callback interests in a server list.
+ */
+void afs_clear_callback_interests(struct afs_net *net, struct afs_server_list *slist)
+{
+ int i;
+
+ for (i = 0; i < slist->nr_servers; i++) {
+ afs_put_cb_interest(net, slist->servers[i].cb_interest);
+ slist->servers[i].cb_interest = NULL;
+ }
+}