diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/afs |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'fs/afs')
36 files changed, 10055 insertions, 0 deletions
diff --git a/fs/afs/Makefile b/fs/afs/Makefile new file mode 100644 index 00000000000..4029c9da4b8 --- /dev/null +++ b/fs/afs/Makefile @@ -0,0 +1,28 @@ +# +# Makefile for Red Hat Linux AFS client. +# + +#CFLAGS += -finstrument-functions + +kafs-objs := \ + callback.o \ + cell.o \ + cmservice.o \ + dir.o \ + file.o \ + fsclient.o \ + inode.o \ + kafsasyncd.o \ + kafstimod.o \ + main.o \ + misc.o \ + mntpt.o \ + proc.o \ + server.o \ + super.o \ + vlclient.o \ + vlocation.o \ + vnode.o \ + volume.o + +obj-$(CONFIG_AFS_FS) := kafs.o diff --git a/fs/afs/cache.h b/fs/afs/cache.h new file mode 100644 index 00000000000..9eb7722b34d --- /dev/null +++ b/fs/afs/cache.h @@ -0,0 +1,27 @@ +/* cache.h: AFS local cache management interface + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_CACHE_H +#define _LINUX_AFS_CACHE_H + +#undef AFS_CACHING_SUPPORT + +#include <linux/mm.h> +#ifdef AFS_CACHING_SUPPORT +#include <linux/cachefs.h> +#endif +#include "types.h" + +#ifdef __KERNEL__ + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_AFS_CACHE_H */ diff --git a/fs/afs/callback.c b/fs/afs/callback.c new file mode 100644 index 00000000000..2fd62f89ae0 --- /dev/null +++ b/fs/afs/callback.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2002 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@cambridge.redhat.com> + * David Howells <dhowells@redhat.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include "server.h" +#include "vnode.h" +#include "internal.h" + +/*****************************************************************************/ +/* + * allow the fileserver to request callback state (re-)initialisation + */ +int SRXAFSCM_InitCallBackState(struct afs_server *server) +{ + struct list_head callbacks; + + _enter("%p", server); + + INIT_LIST_HEAD(&callbacks); + + /* transfer the callback list from the server to a temp holding area */ + spin_lock(&server->cb_lock); + + list_add(&callbacks, &server->cb_promises); + list_del_init(&server->cb_promises); + + /* munch our way through the list, grabbing the inode, dropping all the + * locks and regetting them in the right order + */ + while (!list_empty(&callbacks)) { + struct afs_vnode *vnode; + struct inode *inode; + + vnode = list_entry(callbacks.next, struct afs_vnode, cb_link); + list_del_init(&vnode->cb_link); + + /* try and grab the inode - may fail */ + inode = igrab(AFS_VNODE_TO_I(vnode)); + if (inode) { + int release = 0; + + spin_unlock(&server->cb_lock); + spin_lock(&vnode->lock); + + if (vnode->cb_server == server) { + vnode->cb_server = NULL; + afs_kafstimod_del_timer(&vnode->cb_timeout); + spin_lock(&afs_cb_hash_lock); + list_del_init(&vnode->cb_hash_link); + spin_unlock(&afs_cb_hash_lock); + release = 1; + } + + spin_unlock(&vnode->lock); + + iput(inode); + afs_put_server(server); + + spin_lock(&server->cb_lock); + } + } + + spin_unlock(&server->cb_lock); + + _leave(" = 0"); + return 0; +} /* end SRXAFSCM_InitCallBackState() */ + +/*****************************************************************************/ +/* + * allow the fileserver to break callback promises + */ +int SRXAFSCM_CallBack(struct afs_server *server, size_t count, + struct afs_callback callbacks[]) +{ + _enter("%p,%u,", server, count); + + for (; count > 0; callbacks++, count--) { + struct afs_vnode *vnode = NULL; + struct inode *inode = NULL; + int valid = 0; + + _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->version, + callbacks->expiry, + callbacks->type + ); + + /* find the inode for this fid */ + spin_lock(&afs_cb_hash_lock); + + list_for_each_entry(vnode, + &afs_cb_hash(server, &callbacks->fid), + cb_hash_link) { + if (memcmp(&vnode->fid, &callbacks->fid, + sizeof(struct afs_fid)) != 0) + continue; + + /* right vnode, but is it same server? */ + if (vnode->cb_server != server) + break; /* no */ + + /* try and nail the inode down */ + inode = igrab(AFS_VNODE_TO_I(vnode)); + break; + } + + spin_unlock(&afs_cb_hash_lock); + + if (inode) { + /* we've found the record for this vnode */ + spin_lock(&vnode->lock); + if (vnode->cb_server == server) { + /* the callback _is_ on the calling server */ + vnode->cb_server = NULL; + valid = 1; + + afs_kafstimod_del_timer(&vnode->cb_timeout); + vnode->flags |= AFS_VNODE_CHANGED; + + spin_lock(&server->cb_lock); + list_del_init(&vnode->cb_link); + spin_unlock(&server->cb_lock); + + spin_lock(&afs_cb_hash_lock); + list_del_init(&vnode->cb_hash_link); + spin_unlock(&afs_cb_hash_lock); + } + spin_unlock(&vnode->lock); + + if (valid) { + invalidate_remote_inode(inode); + afs_put_server(server); + } + iput(inode); + } + } + + _leave(" = 0"); + return 0; +} /* end SRXAFSCM_CallBack() */ + +/*****************************************************************************/ +/* + * allow the fileserver to see if the cache manager is still alive + */ +int SRXAFSCM_Probe(struct afs_server *server) +{ + _debug("SRXAFSCM_Probe(%p)\n", server); + return 0; +} /* end SRXAFSCM_Probe() */ diff --git a/fs/afs/cell.c b/fs/afs/cell.c new file mode 100644 index 00000000000..009a9ae88d6 --- /dev/null +++ b/fs/afs/cell.c @@ -0,0 +1,569 @@ +/* cell.c: AFS cell and server record management + * + * Copyright (C) 2002 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/module.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <rxrpc/peer.h> +#include <rxrpc/connection.h> +#include "volume.h" +#include "cell.h" +#include "server.h" +#include "transport.h" +#include "vlclient.h" +#include "kafstimod.h" +#include "super.h" +#include "internal.h" + +DECLARE_RWSEM(afs_proc_cells_sem); +LIST_HEAD(afs_proc_cells); + +static struct list_head afs_cells = LIST_HEAD_INIT(afs_cells); +static DEFINE_RWLOCK(afs_cells_lock); +static DECLARE_RWSEM(afs_cells_sem); /* add/remove serialisation */ +static struct afs_cell *afs_cell_root; + +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_cell_cache_match(void *target, + const void *entry); +static void afs_cell_cache_update(void *source, void *entry); + +struct cachefs_index_def afs_cache_cell_index_def = { + .name = "cell_ix", + .data_size = sizeof(struct afs_cache_cell), + .keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 }, + .match = afs_cell_cache_match, + .update = afs_cell_cache_update, +}; +#endif + +/*****************************************************************************/ +/* + * create a cell record + * - "name" is the name of the cell + * - "vllist" is a colon separated list of IP addresses in "a.b.c.d" format + */ +int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell) +{ + struct afs_cell *cell; + char *next; + int ret; + + _enter("%s", name); + + BUG_ON(!name); /* TODO: want to look up "this cell" in the cache */ + + /* allocate and initialise a cell record */ + cell = kmalloc(sizeof(struct afs_cell) + strlen(name) + 1, GFP_KERNEL); + if (!cell) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + down_write(&afs_cells_sem); + + memset(cell, 0, sizeof(struct afs_cell)); + atomic_set(&cell->usage, 0); + + INIT_LIST_HEAD(&cell->link); + + rwlock_init(&cell->sv_lock); + INIT_LIST_HEAD(&cell->sv_list); + INIT_LIST_HEAD(&cell->sv_graveyard); + spin_lock_init(&cell->sv_gylock); + + init_rwsem(&cell->vl_sem); + INIT_LIST_HEAD(&cell->vl_list); + INIT_LIST_HEAD(&cell->vl_graveyard); + spin_lock_init(&cell->vl_gylock); + + strcpy(cell->name,name); + + /* fill in the VL server list from the rest of the string */ + ret = -EINVAL; + do { + unsigned a, b, c, d; + + next = strchr(vllist, ':'); + if (next) + *next++ = 0; + + if (sscanf(vllist, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + goto badaddr; + + if (a > 255 || b > 255 || c > 255 || d > 255) + goto badaddr; + + cell->vl_addrs[cell->vl_naddrs++].s_addr = + htonl((a << 24) | (b << 16) | (c << 8) | d); + + if (cell->vl_naddrs >= AFS_CELL_MAX_ADDRS) + break; + + } while(vllist = next, vllist); + + /* add a proc dir for this cell */ + ret = afs_proc_cell_setup(cell); + if (ret < 0) + goto error; + +#ifdef AFS_CACHING_SUPPORT + /* put it up for caching */ + cachefs_acquire_cookie(afs_cache_netfs.primary_index, + &afs_vlocation_cache_index_def, + cell, + &cell->cache); +#endif + + /* add to the cell lists */ + write_lock(&afs_cells_lock); + list_add_tail(&cell->link, &afs_cells); + write_unlock(&afs_cells_lock); + + down_write(&afs_proc_cells_sem); + list_add_tail(&cell->proc_link, &afs_proc_cells); + up_write(&afs_proc_cells_sem); + + *_cell = cell; + up_write(&afs_cells_sem); + + _leave(" = 0 (%p)", cell); + return 0; + + badaddr: + printk(KERN_ERR "kAFS: bad VL server IP address: '%s'\n", vllist); + error: + up_write(&afs_cells_sem); + kfree(cell); + _leave(" = %d", ret); + return ret; +} /* end afs_cell_create() */ + +/*****************************************************************************/ +/* + * initialise the cell database from module parameters + */ +int afs_cell_init(char *rootcell) +{ + struct afs_cell *old_root, *new_root; + char *cp; + int ret; + + _enter(""); + + if (!rootcell) { + /* module is loaded with no parameters, or built statically. + * - in the future we might initialize cell DB here. + */ + _leave(" = 0 (but no root)"); + return 0; + } + + cp = strchr(rootcell, ':'); + if (!cp) { + printk(KERN_ERR "kAFS: no VL server IP addresses specified\n"); + _leave(" = %d (no colon)", -EINVAL); + return -EINVAL; + } + + /* allocate a cell record for the root cell */ + *cp++ = 0; + ret = afs_cell_create(rootcell, cp, &new_root); + if (ret < 0) { + _leave(" = %d", ret); + return ret; + } + + /* as afs_put_cell() takes locks by itself, we have to do + * a little gymnastics to be race-free. + */ + afs_get_cell(new_root); + + write_lock(&afs_cells_lock); + while (afs_cell_root) { + old_root = afs_cell_root; + afs_cell_root = NULL; + write_unlock(&afs_cells_lock); + afs_put_cell(old_root); + write_lock(&afs_cells_lock); + } + afs_cell_root = new_root; + write_unlock(&afs_cells_lock); + + _leave(" = %d", ret); + return ret; + +} /* end afs_cell_init() */ + +/*****************************************************************************/ +/* + * lookup a cell record + */ +int afs_cell_lookup(const char *name, unsigned namesz, struct afs_cell **_cell) +{ + struct afs_cell *cell; + int ret; + + _enter("\"%*.*s\",", namesz, namesz, name ? name : ""); + + *_cell = NULL; + + if (name) { + /* if the cell was named, look for it in the cell record list */ + ret = -ENOENT; + cell = NULL; + read_lock(&afs_cells_lock); + + list_for_each_entry(cell, &afs_cells, link) { + if (strncmp(cell->name, name, namesz) == 0) { + afs_get_cell(cell); + goto found; + } + } + cell = NULL; + found: + + read_unlock(&afs_cells_lock); + + if (cell) + ret = 0; + } + else { + read_lock(&afs_cells_lock); + + cell = afs_cell_root; + if (!cell) { + /* this should not happen unless user tries to mount + * when root cell is not set. Return an impossibly + * bizzare errno to alert the user. Things like + * ENOENT might be "more appropriate" but they happen + * for other reasons. + */ + ret = -EDESTADDRREQ; + } + else { + afs_get_cell(cell); + ret = 0; + } + + read_unlock(&afs_cells_lock); + } + + *_cell = cell; + _leave(" = %d (%p)", ret, cell); + return ret; + +} /* end afs_cell_lookup() */ + +/*****************************************************************************/ +/* + * try and get a cell record + */ +struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell) +{ + struct afs_cell *cell; + + write_lock(&afs_cells_lock); + + cell = *_cell; + if (cell && !list_empty(&cell->link)) + afs_get_cell(cell); + else + cell = NULL; + + write_unlock(&afs_cells_lock); + + return cell; +} /* end afs_get_cell_maybe() */ + +/*****************************************************************************/ +/* + * destroy a cell record + */ +void afs_put_cell(struct afs_cell *cell) +{ + if (!cell) + return; + + _enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name); + + /* sanity check */ + BUG_ON(atomic_read(&cell->usage) <= 0); + + /* to prevent a race, the decrement and the dequeue must be effectively + * atomic */ + write_lock(&afs_cells_lock); + + if (likely(!atomic_dec_and_test(&cell->usage))) { + write_unlock(&afs_cells_lock); + _leave(""); + return; + } + + write_unlock(&afs_cells_lock); + + BUG_ON(!list_empty(&cell->sv_list)); + BUG_ON(!list_empty(&cell->sv_graveyard)); + BUG_ON(!list_empty(&cell->vl_list)); + BUG_ON(!list_empty(&cell->vl_graveyard)); + + _leave(" [unused]"); +} /* end afs_put_cell() */ + +/*****************************************************************************/ +/* + * destroy a cell record + */ +static void afs_cell_destroy(struct afs_cell *cell) +{ + _enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name); + + /* to prevent a race, the decrement and the dequeue must be effectively + * atomic */ + write_lock(&afs_cells_lock); + + /* sanity check */ + BUG_ON(atomic_read(&cell->usage) != 0); + + list_del_init(&cell->link); + + write_unlock(&afs_cells_lock); + + down_write(&afs_cells_sem); + + afs_proc_cell_remove(cell); + + down_write(&afs_proc_cells_sem); + list_del_init(&cell->proc_link); + up_write(&afs_proc_cells_sem); + +#ifdef AFS_CACHING_SUPPORT + cachefs_relinquish_cookie(cell->cache, 0); +#endif + + up_write(&afs_cells_sem); + + BUG_ON(!list_empty(&cell->sv_list)); + BUG_ON(!list_empty(&cell->sv_graveyard)); + BUG_ON(!list_empty(&cell->vl_list)); + BUG_ON(!list_empty(&cell->vl_graveyard)); + + /* finish cleaning up the cell */ + kfree(cell); + + _leave(" [destroyed]"); +} /* end afs_cell_destroy() */ + +/*****************************************************************************/ +/* + * lookup the server record corresponding to an Rx RPC peer + */ +int afs_server_find_by_peer(const struct rxrpc_peer *peer, + struct afs_server **_server) +{ + struct afs_server *server; + struct afs_cell *cell; + + _enter("%p{a=%08x},", peer, ntohl(peer->addr.s_addr)); + + /* search the cell list */ + read_lock(&afs_cells_lock); + + list_for_each_entry(cell, &afs_cells, link) { + + _debug("? cell %s",cell->name); + + write_lock(&cell->sv_lock); + + /* check the active list */ + list_for_each_entry(server, &cell->sv_list, link) { + _debug("?? server %08x", ntohl(server->addr.s_addr)); + + if (memcmp(&server->addr, &peer->addr, + sizeof(struct in_addr)) == 0) + goto found_server; + } + + /* check the inactive list */ + spin_lock(&cell->sv_gylock); + list_for_each_entry(server, &cell->sv_graveyard, link) { + _debug("?? dead server %08x", + ntohl(server->addr.s_addr)); + + if (memcmp(&server->addr, &peer->addr, + sizeof(struct in_addr)) == 0) + goto found_dead_server; + } + spin_unlock(&cell->sv_gylock); + + write_unlock(&cell->sv_lock); + } + read_unlock(&afs_cells_lock); + + _leave(" = -ENOENT"); + return -ENOENT; + + /* we found it in the graveyard - resurrect it */ + found_dead_server: + list_del(&server->link); + list_add_tail(&server->link, &cell->sv_list); + afs_get_server(server); + afs_kafstimod_del_timer(&server->timeout); + spin_unlock(&cell->sv_gylock); + goto success; + + /* we found it - increment its ref count and return it */ + found_server: + afs_get_server(server); + + success: + write_unlock(&cell->sv_lock); + read_unlock(&afs_cells_lock); + + *_server = server; + _leave(" = 0 (s=%p c=%p)", server, cell); + return 0; + +} /* end afs_server_find_by_peer() */ + +/*****************************************************************************/ +/* + * purge in-memory cell database on module unload or afs_init() failure + * - the timeout daemon is stopped before calling this + */ +void afs_cell_purge(void) +{ + struct afs_vlocation *vlocation; + struct afs_cell *cell; + + _enter(""); + + afs_put_cell(afs_cell_root); + + while (!list_empty(&afs_cells)) { + cell = NULL; + + /* remove the next cell from the front of the list */ + write_lock(&afs_cells_lock); + + if (!list_empty(&afs_cells)) { + cell = list_entry(afs_cells.next, + struct afs_cell, link); + list_del_init(&cell->link); + } + + write_unlock(&afs_cells_lock); + + if (cell) { + _debug("PURGING CELL %s (%d)", + cell->name, atomic_read(&cell->usage)); + + BUG_ON(!list_empty(&cell->sv_list)); + BUG_ON(!list_empty(&cell->vl_list)); + + /* purge the cell's VL graveyard list */ + _debug(" - clearing VL graveyard"); + + spin_lock(&cell->vl_gylock); + + while (!list_empty(&cell->vl_graveyard)) { + vlocation = list_entry(cell->vl_graveyard.next, + struct afs_vlocation, + link); + list_del_init(&vlocation->link); + + afs_kafstimod_del_timer(&vlocation->timeout); + + spin_unlock(&cell->vl_gylock); + + afs_vlocation_do_timeout(vlocation); + /* TODO: race if move to use krxtimod instead + * of kafstimod */ + + spin_lock(&cell->vl_gylock); + } + + spin_unlock(&cell->vl_gylock); + + /* purge the cell's server graveyard list */ + _debug(" - clearing server graveyard"); + + spin_lock(&cell->sv_gylock); + + while (!list_empty(&cell->sv_graveyard)) { + struct afs_server *server; + + server = list_entry(cell->sv_graveyard.next, + struct afs_server, link); + list_del_init(&server->link); + + afs_kafstimod_del_timer(&server->timeout); + + spin_unlock(&cell->sv_gylock); + + afs_server_do_timeout(server); + + spin_lock(&cell->sv_gylock); + } + + spin_unlock(&cell->sv_gylock); + + /* now the cell should be left with no references */ + afs_cell_destroy(cell); + } + } + + _leave(""); +} /* end afs_cell_purge() */ + +/*****************************************************************************/ +/* + * match a cell record obtained from the cache + */ +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_cell_cache_match(void *target, + const void *entry) +{ + const struct afs_cache_cell *ccell = entry; + struct afs_cell *cell = target; + + _enter("{%s},{%s}", ccell->name, cell->name); + + if (strncmp(ccell->name, cell->name, sizeof(ccell->name)) == 0) { + _leave(" = SUCCESS"); + return CACHEFS_MATCH_SUCCESS; + } + + _leave(" = FAILED"); + return CACHEFS_MATCH_FAILED; +} /* end afs_cell_cache_match() */ +#endif + +/*****************************************************************************/ +/* + * update a cell record in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_cell_cache_update(void *source, void *entry) +{ + struct afs_cache_cell *ccell = entry; + struct afs_cell *cell = source; + + _enter("%p,%p", source, entry); + + strncpy(ccell->name, cell->name, sizeof(ccell->name)); + + memcpy(ccell->vl_servers, + cell->vl_addrs, + min(sizeof(ccell->vl_servers), sizeof(cell->vl_addrs))); + +} /* end afs_cell_cache_update() */ +#endif diff --git a/fs/afs/cell.h b/fs/afs/cell.h new file mode 100644 index 00000000000..48349108fb0 --- /dev/null +++ b/fs/afs/cell.h @@ -0,0 +1,78 @@ +/* cell.h: AFS cell record + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_CELL_H +#define _LINUX_AFS_CELL_H + +#include "types.h" +#include "cache.h" + +#define AFS_CELL_MAX_ADDRS 15 + +extern volatile int afs_cells_being_purged; /* T when cells are being purged by rmmod */ + +/*****************************************************************************/ +/* + * entry in the cached cell catalogue + */ +struct afs_cache_cell +{ + char name[64]; /* cell name (padded with NULs) */ + struct in_addr vl_servers[15]; /* cached cell VL servers */ +}; + +/*****************************************************************************/ +/* + * AFS cell record + */ +struct afs_cell +{ + atomic_t usage; + struct list_head link; /* main cell list link */ + struct list_head proc_link; /* /proc cell list link */ + struct proc_dir_entry *proc_dir; /* /proc dir for this cell */ +#ifdef AFS_CACHING_SUPPORT + struct cachefs_cookie *cache; /* caching cookie */ +#endif + + /* server record management */ + rwlock_t sv_lock; /* active server list lock */ + struct list_head sv_list; /* active server list */ + struct list_head sv_graveyard; /* inactive server list */ + spinlock_t sv_gylock; /* inactive server list lock */ + + /* volume location record management */ + struct rw_semaphore vl_sem; /* volume management serialisation semaphore */ + struct list_head vl_list; /* cell's active VL record list */ + struct list_head vl_graveyard; /* cell's inactive VL record list */ + spinlock_t vl_gylock; /* graveyard lock */ + unsigned short vl_naddrs; /* number of VL servers in addr list */ + unsigned short vl_curr_svix; /* current server index */ + struct in_addr vl_addrs[AFS_CELL_MAX_ADDRS]; /* cell VL server addresses */ + + char name[0]; /* cell name - must go last */ +}; + +extern int afs_cell_init(char *rootcell); + +extern int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell); + +extern int afs_cell_lookup(const char *name, unsigned nmsize, struct afs_cell **_cell); + +#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0) + +extern struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell); + +extern void afs_put_cell(struct afs_cell *cell); + +extern void afs_cell_purge(void); + +#endif /* _LINUX_AFS_CELL_H */ diff --git a/fs/afs/cmservice.c b/fs/afs/cmservice.c new file mode 100644 index 00000000000..0a57fd7c726 --- /dev/null +++ b/fs/afs/cmservice.c @@ -0,0 +1,652 @@ +/* cmservice.c: AFS Cache Manager Service + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include "server.h" +#include "cell.h" +#include "transport.h" +#include <rxrpc/rxrpc.h> +#include <rxrpc/transport.h> +#include <rxrpc/connection.h> +#include <rxrpc/call.h> +#include "cmservice.h" +#include "internal.h" + +static unsigned afscm_usage; /* AFS cache manager usage count */ +static struct rw_semaphore afscm_sem; /* AFS cache manager start/stop semaphore */ + +static int afscm_new_call(struct rxrpc_call *call); +static void afscm_attention(struct rxrpc_call *call); +static void afscm_error(struct rxrpc_call *call); +static void afscm_aemap(struct rxrpc_call *call); + +static void _SRXAFSCM_CallBack(struct rxrpc_call *call); +static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call); +static void _SRXAFSCM_Probe(struct rxrpc_call *call); + +typedef void (*_SRXAFSCM_xxxx_t)(struct rxrpc_call *call); + +static const struct rxrpc_operation AFSCM_ops[] = { + { + .id = 204, + .asize = RXRPC_APP_MARK_EOF, + .name = "CallBack", + .user = _SRXAFSCM_CallBack, + }, + { + .id = 205, + .asize = RXRPC_APP_MARK_EOF, + .name = "InitCallBackState", + .user = _SRXAFSCM_InitCallBackState, + }, + { + .id = 206, + .asize = RXRPC_APP_MARK_EOF, + .name = "Probe", + .user = _SRXAFSCM_Probe, + }, +#if 0 + { + .id = 207, + .asize = RXRPC_APP_MARK_EOF, + .name = "GetLock", + .user = _SRXAFSCM_GetLock, + }, + { + .id = 208, + .asize = RXRPC_APP_MARK_EOF, + .name = "GetCE", + .user = _SRXAFSCM_GetCE, + }, + { + .id = 209, + .asize = RXRPC_APP_MARK_EOF, + .name = "GetXStatsVersion", + .user = _SRXAFSCM_GetXStatsVersion, + }, + { + .id = 210, + .asize = RXRPC_APP_MARK_EOF, + .name = "GetXStats", + .user = _SRXAFSCM_GetXStats, + } +#endif +}; + +static struct rxrpc_service AFSCM_service = { + .name = "AFS/CM", + .owner = THIS_MODULE, + .link = LIST_HEAD_INIT(AFSCM_service.link), + .new_call = afscm_new_call, + .service_id = 1, + .attn_func = afscm_attention, + .error_func = afscm_error, + .aemap_func = afscm_aemap, + .ops_begin = &AFSCM_ops[0], + .ops_end = &AFSCM_ops[sizeof(AFSCM_ops) / sizeof(AFSCM_ops[0])], +}; + +static DECLARE_COMPLETION(kafscmd_alive); +static DECLARE_COMPLETION(kafscmd_dead); +static DECLARE_WAIT_QUEUE_HEAD(kafscmd_sleepq); +static LIST_HEAD(kafscmd_attention_list); +static LIST_HEAD(afscm_calls); +static DEFINE_SPINLOCK(afscm_calls_lock); +static DEFINE_SPINLOCK(kafscmd_attention_lock); +static int kafscmd_die; + +/*****************************************************************************/ +/* + * AFS Cache Manager kernel thread + */ +static int kafscmd(void *arg) +{ + DECLARE_WAITQUEUE(myself, current); + + struct rxrpc_call *call; + _SRXAFSCM_xxxx_t func; + int die; + + printk("kAFS: Started kafscmd %d\n", current->pid); + + daemonize("kafscmd"); + + complete(&kafscmd_alive); + + /* loop around looking for things to attend to */ + do { + if (list_empty(&kafscmd_attention_list)) { + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&kafscmd_sleepq, &myself); + + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (!list_empty(&kafscmd_attention_list) || + signal_pending(current) || + kafscmd_die) + break; + + schedule(); + } + + remove_wait_queue(&kafscmd_sleepq, &myself); + set_current_state(TASK_RUNNING); + } + + die = kafscmd_die; + + /* dequeue the next call requiring attention */ + call = NULL; + spin_lock(&kafscmd_attention_lock); + + if (!list_empty(&kafscmd_attention_list)) { + call = list_entry(kafscmd_attention_list.next, + struct rxrpc_call, + app_attn_link); + list_del_init(&call->app_attn_link); + die = 0; + } + + spin_unlock(&kafscmd_attention_lock); + + if (call) { + /* act upon it */ + _debug("@@@ Begin Attend Call %p", call); + + func = call->app_user; + if (func) + func(call); + + rxrpc_put_call(call); + + _debug("@@@ End Attend Call %p", call); + } + + } while(!die); + + /* and that's all */ + complete_and_exit(&kafscmd_dead, 0); + +} /* end kafscmd() */ + +/*****************************************************************************/ +/* + * handle a call coming in to the cache manager + * - if I want to keep the call, I must increment its usage count + * - the return value will be negated and passed back in an abort packet if + * non-zero + * - serialised by virtue of there only being one krxiod + */ +static int afscm_new_call(struct rxrpc_call *call) +{ + _enter("%p{cid=%u u=%d}", + call, ntohl(call->call_id), atomic_read(&call->usage)); + + rxrpc_get_call(call); + + /* add to my current call list */ + spin_lock(&afscm_calls_lock); + list_add(&call->app_link,&afscm_calls); + spin_unlock(&afscm_calls_lock); + + _leave(" = 0"); + return 0; + +} /* end afscm_new_call() */ + +/*****************************************************************************/ +/* + * queue on the kafscmd queue for attention + */ +static void afscm_attention(struct rxrpc_call *call) +{ + _enter("%p{cid=%u u=%d}", + call, ntohl(call->call_id), atomic_read(&call->usage)); + + spin_lock(&kafscmd_attention_lock); + + if (list_empty(&call->app_attn_link)) { + list_add_tail(&call->app_attn_link, &kafscmd_attention_list); + rxrpc_get_call(call); + } + + spin_unlock(&kafscmd_attention_lock); + + wake_up(&kafscmd_sleepq); + + _leave(" {u=%d}", atomic_read(&call->usage)); +} /* end afscm_attention() */ + +/*****************************************************************************/ +/* + * handle my call being aborted + * - clean up, dequeue and put my ref to the call + */ +static void afscm_error(struct rxrpc_call *call) +{ + int removed; + + _enter("%p{est=%s ac=%u er=%d}", + call, + rxrpc_call_error_states[call->app_err_state], + call->app_abort_code, + call->app_errno); + + spin_lock(&kafscmd_attention_lock); + + if (list_empty(&call->app_attn_link)) { + list_add_tail(&call->app_attn_link, &kafscmd_attention_list); + rxrpc_get_call(call); + } + + spin_unlock(&kafscmd_attention_lock); + + removed = 0; + spin_lock(&afscm_calls_lock); + if (!list_empty(&call->app_link)) { + list_del_init(&call->app_link); + removed = 1; + } + spin_unlock(&afscm_calls_lock); + + if (removed) + rxrpc_put_call(call); + + wake_up(&kafscmd_sleepq); + + _leave(""); +} /* end afscm_error() */ + +/*****************************************************************************/ +/* + * map afs abort codes to/from Linux error codes + * - called with call->lock held + */ +static void afscm_aemap(struct rxrpc_call *call) +{ + switch (call->app_err_state) { + case RXRPC_ESTATE_LOCAL_ABORT: + call->app_abort_code = -call->app_errno; + break; + case RXRPC_ESTATE_PEER_ABORT: + call->app_errno = -ECONNABORTED; + break; + default: + break; + } +} /* end afscm_aemap() */ + +/*****************************************************************************/ +/* + * start the cache manager service if not already started + */ +int afscm_start(void) +{ + int ret; + + down_write(&afscm_sem); + if (!afscm_usage) { + ret = kernel_thread(kafscmd, NULL, 0); + if (ret < 0) + goto out; + + wait_for_completion(&kafscmd_alive); + + ret = rxrpc_add_service(afs_transport, &AFSCM_service); + if (ret < 0) + goto kill; + + afs_kafstimod_add_timer(&afs_mntpt_expiry_timer, + afs_mntpt_expiry_timeout * HZ); + } + + afscm_usage++; + up_write(&afscm_sem); + + return 0; + + kill: + kafscmd_die = 1; + wake_up(&kafscmd_sleepq); + wait_for_completion(&kafscmd_dead); + + out: + up_write(&afscm_sem); + return ret; + +} /* end afscm_start() */ + +/*****************************************************************************/ +/* + * stop the cache manager service + */ +void afscm_stop(void) +{ + struct rxrpc_call *call; + + down_write(&afscm_sem); + + BUG_ON(afscm_usage == 0); + afscm_usage--; + + if (afscm_usage == 0) { + /* don't want more incoming calls */ + rxrpc_del_service(afs_transport, &AFSCM_service); + + /* abort any calls I've still got open (the afscm_error() will + * dequeue them) */ + spin_lock(&afscm_calls_lock); + while (!list_empty(&afscm_calls)) { + call = list_entry(afscm_calls.next, + struct rxrpc_call, + app_link); + + list_del_init(&call->app_link); + rxrpc_get_call(call); + spin_unlock(&afscm_calls_lock); + + rxrpc_call_abort(call, -ESRCH); /* abort, dequeue and + * put */ + + _debug("nuking active call %08x.%d", + ntohl(call->conn->conn_id), + ntohl(call->call_id)); + rxrpc_put_call(call); + rxrpc_put_call(call); + + spin_lock(&afscm_calls_lock); + } + spin_unlock(&afscm_calls_lock); + + /* get rid of my daemon */ + kafscmd_die = 1; + wake_up(&kafscmd_sleepq); + wait_for_completion(&kafscmd_dead); + + /* dispose of any calls waiting for attention */ + spin_lock(&kafscmd_attention_lock); + while (!list_empty(&kafscmd_attention_list)) { + call = list_entry(kafscmd_attention_list.next, + struct rxrpc_call, + app_attn_link); + + list_del_init(&call->app_attn_link); + spin_unlock(&kafscmd_attention_lock); + + rxrpc_put_call(call); + + spin_lock(&kafscmd_attention_lock); + } + spin_unlock(&kafscmd_attention_lock); + + afs_kafstimod_del_timer(&afs_mntpt_expiry_timer); + } + + up_write(&afscm_sem); + +} /* end afscm_stop() */ + +/*****************************************************************************/ +/* + * handle the fileserver breaking a set of callbacks + */ +static void _SRXAFSCM_CallBack(struct rxrpc_call *call) +{ + struct afs_server *server; + size_t count, qty, tmp; + int ret = 0, removed; + + _enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]); + + server = afs_server_get_from_peer(call->conn->peer); + + switch (call->app_call_state) { + /* we've received the last packet + * - drain all the data from the call and send the reply + */ + case RXRPC_CSTATE_SRVR_GOT_ARGS: + ret = -EBADMSG; + qty = call->app_ready_qty; + if (qty < 8 || qty > 50 * (6 * 4) + 8) + break; + + { + struct afs_callback *cb, *pcb; + int loop; + __be32 *fp, *bp; + + fp = rxrpc_call_alloc_scratch(call, qty); + + /* drag the entire argument block out to the scratch + * space */ + ret = rxrpc_call_read_data(call, fp, qty, 0); + if (ret < 0) + break; + + /* and unmarshall the parameter block */ + ret = -EBADMSG; + count = ntohl(*fp++); + if (count>AFSCBMAX || + (count * (3 * 4) + 8 != qty && + count * (6 * 4) + 8 != qty)) + break; + + bp = fp + count*3; + tmp = ntohl(*bp++); + if (tmp > 0 && tmp != count) + break; + if (tmp == 0) + bp = NULL; + + pcb = cb = rxrpc_call_alloc_scratch_s( + call, struct afs_callback); + + for (loop = count - 1; loop >= 0; loop--) { + pcb->fid.vid = ntohl(*fp++); + pcb->fid.vnode = ntohl(*fp++); + pcb->fid.unique = ntohl(*fp++); + if (bp) { + pcb->version = ntohl(*bp++); + pcb->expiry = ntohl(*bp++); + pcb->type = ntohl(*bp++); + } + else { + pcb->version = 0; + pcb->expiry = 0; + pcb->type = AFSCM_CB_UNTYPED; + } + pcb++; + } + + /* invoke the actual service routine */ + ret = SRXAFSCM_CallBack(server, count, cb); + if (ret < 0) + break; + } + + /* send the reply */ + ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET, + GFP_KERNEL, 0, &count); + if (ret < 0) + break; + break; + + /* operation complete */ + case RXRPC_CSTATE_COMPLETE: + call->app_user = NULL; + removed = 0; + spin_lock(&afscm_calls_lock); + if (!list_empty(&call->app_link)) { + list_del_init(&call->app_link); + removed = 1; + } + spin_unlock(&afscm_calls_lock); + + if (removed) + rxrpc_put_call(call); + break; + + /* operation terminated on error */ + case RXRPC_CSTATE_ERROR: + call->app_user = NULL; + break; + + default: + break; + } + + if (ret < 0) + rxrpc_call_abort(call, ret); + + afs_put_server(server); + + _leave(" = %d", ret); + +} /* end _SRXAFSCM_CallBack() */ + +/*****************************************************************************/ +/* + * handle the fileserver asking us to initialise our callback state + */ +static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call) +{ + struct afs_server *server; + size_t count; + int ret = 0, removed; + + _enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]); + + server = afs_server_get_from_peer(call->conn->peer); + + switch (call->app_call_state) { + /* we've received the last packet - drain all the data from the + * call */ + case RXRPC_CSTATE_SRVR_GOT_ARGS: + /* shouldn't be any args */ + ret = -EBADMSG; + break; + + /* send the reply when asked for it */ + case RXRPC_CSTATE_SRVR_SND_REPLY: + /* invoke the actual service routine */ + ret = SRXAFSCM_InitCallBackState(server); + if (ret < 0) + break; + + ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET, + GFP_KERNEL, 0, &count); + if (ret < 0) + break; + break; + + /* operation complete */ + case RXRPC_CSTATE_COMPLETE: + call->app_user = NULL; + removed = 0; + spin_lock(&afscm_calls_lock); + if (!list_empty(&call->app_link)) { + list_del_init(&call->app_link); + removed = 1; + } + spin_unlock(&afscm_calls_lock); + + if (removed) + rxrpc_put_call(call); + break; + + /* operation terminated on error */ + case RXRPC_CSTATE_ERROR: + call->app_user = NULL; + break; + + default: + break; + } + + if (ret < 0) + rxrpc_call_abort(call, ret); + + afs_put_server(server); + + _leave(" = %d", ret); + +} /* end _SRXAFSCM_InitCallBackState() */ + +/*****************************************************************************/ +/* + * handle a probe from a fileserver + */ +static void _SRXAFSCM_Probe(struct rxrpc_call *call) +{ + struct afs_server *server; + size_t count; + int ret = 0, removed; + + _enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]); + + server = afs_server_get_from_peer(call->conn->peer); + + switch (call->app_call_state) { + /* we've received the last packet - drain all the data from the + * call */ + case RXRPC_CSTATE_SRVR_GOT_ARGS: + /* shouldn't be any args */ + ret = -EBADMSG; + break; + + /* send the reply when asked for it */ + case RXRPC_CSTATE_SRVR_SND_REPLY: + /* invoke the actual service routine */ + ret = SRXAFSCM_Probe(server); + if (ret < 0) + break; + + ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET, + GFP_KERNEL, 0, &count); + if (ret < 0) + break; + break; + + /* operation complete */ + case RXRPC_CSTATE_COMPLETE: + call->app_user = NULL; + removed = 0; + spin_lock(&afscm_calls_lock); + if (!list_empty(&call->app_link)) { + list_del_init(&call->app_link); + removed = 1; + } + spin_unlock(&afscm_calls_lock); + + if (removed) + rxrpc_put_call(call); + break; + + /* operation terminated on error */ + case RXRPC_CSTATE_ERROR: + call->app_user = NULL; + break; + + default: + break; + } + + if (ret < 0) + rxrpc_call_abort(call, ret); + + afs_put_server(server); + + _leave(" = %d", ret); + +} /* end _SRXAFSCM_Probe() */ diff --git a/fs/afs/cmservice.h b/fs/afs/cmservice.h new file mode 100644 index 00000000000..af8d4d689cb --- /dev/null +++ b/fs/afs/cmservice.h @@ -0,0 +1,29 @@ +/* cmservice.h: AFS Cache Manager Service declarations + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_CMSERVICE_H +#define _LINUX_AFS_CMSERVICE_H + +#include <rxrpc/transport.h> +#include "types.h" + +/* cache manager start/stop */ +extern int afscm_start(void); +extern void afscm_stop(void); + +/* cache manager server functions */ +extern int SRXAFSCM_InitCallBackState(struct afs_server *server); +extern int SRXAFSCM_CallBack(struct afs_server *server, + size_t count, + struct afs_callback callbacks[]); +extern int SRXAFSCM_Probe(struct afs_server *server); + +#endif /* _LINUX_AFS_CMSERVICE_H */ diff --git a/fs/afs/dir.c b/fs/afs/dir.c new file mode 100644 index 00000000000..6682d6d7f29 --- /dev/null +++ b/fs/afs/dir.c @@ -0,0 +1,666 @@ +/* dir.c: AFS filesystem directory handling + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include <linux/smp_lock.h> +#include "vnode.h" +#include "volume.h" +#include <rxrpc/call.h> +#include "super.h" +#include "internal.h" + +static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd); +static int afs_dir_open(struct inode *inode, struct file *file); +static int afs_dir_readdir(struct file *file, void *dirent, filldir_t filldir); +static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd); +static int afs_d_delete(struct dentry *dentry); +static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen, + loff_t fpos, ino_t ino, unsigned dtype); + +struct file_operations afs_dir_file_operations = { + .open = afs_dir_open, + .readdir = afs_dir_readdir, +}; + +struct inode_operations afs_dir_inode_operations = { + .lookup = afs_dir_lookup, + .getattr = afs_inode_getattr, +#if 0 /* TODO */ + .create = afs_dir_create, + .link = afs_dir_link, + .unlink = afs_dir_unlink, + .symlink = afs_dir_symlink, + .mkdir = afs_dir_mkdir, + .rmdir = afs_dir_rmdir, + .mknod = afs_dir_mknod, + .rename = afs_dir_rename, +#endif +}; + +static struct dentry_operations afs_fs_dentry_operations = { + .d_revalidate = afs_d_revalidate, + .d_delete = afs_d_delete, +}; + +#define AFS_DIR_HASHTBL_SIZE 128 +#define AFS_DIR_DIRENT_SIZE 32 +#define AFS_DIRENT_PER_BLOCK 64 + +union afs_dirent { + struct { + uint8_t valid; + uint8_t unused[1]; + __be16 hash_next; + __be32 vnode; + __be32 unique; + uint8_t name[16]; + uint8_t overflow[4]; /* if any char of the name (inc + * NUL) reaches here, consume + * the next dirent too */ + } u; + uint8_t extended_name[32]; +}; + +/* AFS directory page header (one at the beginning of every 2048-byte chunk) */ +struct afs_dir_pagehdr { + __be16 npages; + __be16 magic; +#define AFS_DIR_MAGIC htons(1234) + uint8_t nentries; + uint8_t bitmap[8]; + uint8_t pad[19]; +}; + +/* directory block layout */ +union afs_dir_block { + + struct afs_dir_pagehdr pagehdr; + + struct { + struct afs_dir_pagehdr pagehdr; + uint8_t alloc_ctrs[128]; + /* dir hash table */ + uint16_t hashtable[AFS_DIR_HASHTBL_SIZE]; + } hdr; + + union afs_dirent dirents[AFS_DIRENT_PER_BLOCK]; +}; + +/* layout on a linux VM page */ +struct afs_dir_page { + union afs_dir_block blocks[PAGE_SIZE / sizeof(union afs_dir_block)]; +}; + +struct afs_dir_lookup_cookie { + struct afs_fid fid; + const char *name; + size_t nlen; + int found; +}; + +/*****************************************************************************/ +/* + * check that a directory page is valid + */ +static inline void afs_dir_check_page(struct inode *dir, struct page *page) +{ + struct afs_dir_page *dbuf; + loff_t latter; + int tmp, qty; + +#if 0 + /* check the page count */ + qty = desc.size / sizeof(dbuf->blocks[0]); + if (qty == 0) + goto error; + + if (page->index==0 && qty!=ntohs(dbuf->blocks[0].pagehdr.npages)) { + printk("kAFS: %s(%lu): wrong number of dir blocks %d!=%hu\n", + __FUNCTION__,dir->i_ino,qty,ntohs(dbuf->blocks[0].pagehdr.npages)); + goto error; + } +#endif + + /* determine how many magic numbers there should be in this page */ + latter = dir->i_size - (page->index << PAGE_CACHE_SHIFT); + if (latter >= PAGE_SIZE) + qty = PAGE_SIZE; + else + qty = latter; + qty /= sizeof(union afs_dir_block); + + /* check them */ + dbuf = page_address(page); + for (tmp = 0; tmp < qty; tmp++) { + if (dbuf->blocks[tmp].pagehdr.magic != AFS_DIR_MAGIC) { + printk("kAFS: %s(%lu): bad magic %d/%d is %04hx\n", + __FUNCTION__, dir->i_ino, tmp, qty, + ntohs(dbuf->blocks[tmp].pagehdr.magic)); + goto error; + } + } + + SetPageChecked(page); + return; + + error: + SetPageChecked(page); + SetPageError(page); + +} /* end afs_dir_check_page() */ + +/*****************************************************************************/ +/* + * discard a page cached in the pagecache + */ +static inline void afs_dir_put_page(struct page *page) +{ + kunmap(page); + page_cache_release(page); + +} /* end afs_dir_put_page() */ + +/*****************************************************************************/ +/* + * get a page into the pagecache + */ +static struct page *afs_dir_get_page(struct inode *dir, unsigned long index) +{ + struct page *page; + + _enter("{%lu},%lu", dir->i_ino, index); + + page = read_cache_page(dir->i_mapping,index, + (filler_t *) dir->i_mapping->a_ops->readpage, + NULL); + if (!IS_ERR(page)) { + wait_on_page_locked(page); + kmap(page); + if (!PageUptodate(page)) + goto fail; + if (!PageChecked(page)) + afs_dir_check_page(dir, page); + if (PageError(page)) + goto fail; + } + return page; + + fail: + afs_dir_put_page(page); + return ERR_PTR(-EIO); +} /* end afs_dir_get_page() */ + +/*****************************************************************************/ +/* + * open an AFS directory file + */ +static int afs_dir_open(struct inode *inode, struct file *file) +{ + _enter("{%lu}", inode->i_ino); + + BUG_ON(sizeof(union afs_dir_block) != 2048); + BUG_ON(sizeof(union afs_dirent) != 32); + + if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED) + return -ENOENT; + + _leave(" = 0"); + return 0; + +} /* end afs_dir_open() */ + +/*****************************************************************************/ +/* + * deal with one block in an AFS directory + */ +static int afs_dir_iterate_block(unsigned *fpos, + union afs_dir_block *block, + unsigned blkoff, + void *cookie, + filldir_t filldir) +{ + union afs_dirent *dire; + unsigned offset, next, curr; + size_t nlen; + int tmp, ret; + + _enter("%u,%x,%p,,",*fpos,blkoff,block); + + curr = (*fpos - blkoff) / sizeof(union afs_dirent); + + /* walk through the block, an entry at a time */ + for (offset = AFS_DIRENT_PER_BLOCK - block->pagehdr.nentries; + offset < AFS_DIRENT_PER_BLOCK; + offset = next + ) { + next = offset + 1; + + /* skip entries marked unused in the bitmap */ + if (!(block->pagehdr.bitmap[offset / 8] & + (1 << (offset % 8)))) { + _debug("ENT[%Zu.%u]: unused\n", + blkoff / sizeof(union afs_dir_block), offset); + if (offset >= curr) + *fpos = blkoff + + next * sizeof(union afs_dirent); + continue; + } + + /* got a valid entry */ + dire = &block->dirents[offset]; + nlen = strnlen(dire->u.name, + sizeof(*block) - + offset * sizeof(union afs_dirent)); + + _debug("ENT[%Zu.%u]: %s %Zu \"%s\"\n", + blkoff / sizeof(union afs_dir_block), offset, + (offset < curr ? "skip" : "fill"), + nlen, dire->u.name); + + /* work out where the next possible entry is */ + for (tmp = nlen; tmp > 15; tmp -= sizeof(union afs_dirent)) { + if (next >= AFS_DIRENT_PER_BLOCK) { + _debug("ENT[%Zu.%u]:" + " %u travelled beyond end dir block" + " (len %u/%Zu)\n", + blkoff / sizeof(union afs_dir_block), + offset, next, tmp, nlen); + return -EIO; + } + if (!(block->pagehdr.bitmap[next / 8] & + (1 << (next % 8)))) { + _debug("ENT[%Zu.%u]:" + " %u unmarked extension (len %u/%Zu)\n", + blkoff / sizeof(union afs_dir_block), + offset, next, tmp, nlen); + return -EIO; + } + + _debug("ENT[%Zu.%u]: ext %u/%Zu\n", + blkoff / sizeof(union afs_dir_block), + next, tmp, nlen); + next++; + } + + /* skip if starts before the current position */ + if (offset < curr) + continue; + + /* found the next entry */ + ret = filldir(cookie, + dire->u.name, + nlen, + blkoff + offset * sizeof(union afs_dirent), + ntohl(dire->u.vnode), + filldir == afs_dir_lookup_filldir ? + ntohl(dire->u.unique) : DT_UNKNOWN); + if (ret < 0) { + _leave(" = 0 [full]"); + return 0; + } + + *fpos = blkoff + next * sizeof(union afs_dirent); + } + + _leave(" = 1 [more]"); + return 1; +} /* end afs_dir_iterate_block() */ + +/*****************************************************************************/ +/* + * read an AFS directory + */ +static int afs_dir_iterate(struct inode *dir, unsigned *fpos, void *cookie, + filldir_t filldir) +{ + union afs_dir_block *dblock; + struct afs_dir_page *dbuf; + struct page *page; + unsigned blkoff, limit; + int ret; + + _enter("{%lu},%u,,", dir->i_ino, *fpos); + + if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) { + _leave(" = -ESTALE"); + return -ESTALE; + } + + /* round the file position up to the next entry boundary */ + *fpos += sizeof(union afs_dirent) - 1; + *fpos &= ~(sizeof(union afs_dirent) - 1); + + /* walk through the blocks in sequence */ + ret = 0; + while (*fpos < dir->i_size) { + blkoff = *fpos & ~(sizeof(union afs_dir_block) - 1); + + /* fetch the appropriate page from the directory */ + page = afs_dir_get_page(dir, blkoff / PAGE_SIZE); + if (IS_ERR(page)) { + ret = PTR_ERR(page); + break; + } + + limit = blkoff & ~(PAGE_SIZE - 1); + + dbuf = page_address(page); + + /* deal with the individual blocks stashed on this page */ + do { + dblock = &dbuf->blocks[(blkoff % PAGE_SIZE) / + sizeof(union afs_dir_block)]; + ret = afs_dir_iterate_block(fpos, dblock, blkoff, + cookie, filldir); + if (ret != 1) { + afs_dir_put_page(page); + goto out; + } + + blkoff += sizeof(union afs_dir_block); + + } while (*fpos < dir->i_size && blkoff < limit); + + afs_dir_put_page(page); + ret = 0; + } + + out: + _leave(" = %d", ret); + return ret; +} /* end afs_dir_iterate() */ + +/*****************************************************************************/ +/* + * read an AFS directory + */ +static int afs_dir_readdir(struct file *file, void *cookie, filldir_t filldir) +{ + unsigned fpos; + int ret; + + _enter("{%Ld,{%lu}}", file->f_pos, file->f_dentry->d_inode->i_ino); + + fpos = file->f_pos; + ret = afs_dir_iterate(file->f_dentry->d_inode, &fpos, cookie, filldir); + file->f_pos = fpos; + + _leave(" = %d", ret); + return ret; +} /* end afs_dir_readdir() */ + +/*****************************************************************************/ +/* + * search the directory for a name + * - if afs_dir_iterate_block() spots this function, it'll pass the FID + * uniquifier through dtype + */ +static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen, + loff_t fpos, ino_t ino, unsigned dtype) +{ + struct afs_dir_lookup_cookie *cookie = _cookie; + + _enter("{%s,%Zu},%s,%u,,%lu,%u", + cookie->name, cookie->nlen, name, nlen, ino, dtype); + + if (cookie->nlen != nlen || memcmp(cookie->name, name, nlen) != 0) { + _leave(" = 0 [no]"); + return 0; + } + + cookie->fid.vnode = ino; + cookie->fid.unique = dtype; + cookie->found = 1; + + _leave(" = -1 [found]"); + return -1; +} /* end afs_dir_lookup_filldir() */ + +/*****************************************************************************/ +/* + * look up an entry in a directory + */ +static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nd) +{ + struct afs_dir_lookup_cookie cookie; + struct afs_super_info *as; + struct afs_vnode *vnode; + struct inode *inode; + unsigned fpos; + int ret; + + _enter("{%lu},%p{%s}", dir->i_ino, dentry, dentry->d_name.name); + + /* insanity checks first */ + BUG_ON(sizeof(union afs_dir_block) != 2048); + BUG_ON(sizeof(union afs_dirent) != 32); + + if (dentry->d_name.len > 255) { + _leave(" = -ENAMETOOLONG"); + return ERR_PTR(-ENAMETOOLONG); + } + + vnode = AFS_FS_I(dir); + if (vnode->flags & AFS_VNODE_DELETED) { + _leave(" = -ESTALE"); + return ERR_PTR(-ESTALE); + } + + as = dir->i_sb->s_fs_info; + + /* search the directory */ + cookie.name = dentry->d_name.name; + cookie.nlen = dentry->d_name.len; + cookie.fid.vid = as->volume->vid; + cookie.found = 0; + + fpos = 0; + ret = afs_dir_iterate(dir, &fpos, &cookie, afs_dir_lookup_filldir); + if (ret < 0) { + _leave(" = %d", ret); + return ERR_PTR(ret); + } + + ret = -ENOENT; + if (!cookie.found) { + _leave(" = %d", ret); + return ERR_PTR(ret); + } + + /* instantiate the dentry */ + ret = afs_iget(dir->i_sb, &cookie.fid, &inode); + if (ret < 0) { + _leave(" = %d", ret); + return ERR_PTR(ret); + } + + dentry->d_op = &afs_fs_dentry_operations; + dentry->d_fsdata = (void *) (unsigned long) vnode->status.version; + + d_add(dentry, inode); + _leave(" = 0 { vn=%u u=%u } -> { ino=%lu v=%lu }", + cookie.fid.vnode, + cookie.fid.unique, + dentry->d_inode->i_ino, + dentry->d_inode->i_version); + + return NULL; +} /* end afs_dir_lookup() */ + +/*****************************************************************************/ +/* + * check that a dentry lookup hit has found a valid entry + * - NOTE! the hit can be a negative hit too, so we can't assume we have an + * inode + * (derived from nfs_lookup_revalidate) + */ +static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd) +{ + struct afs_dir_lookup_cookie cookie; + struct dentry *parent; + struct inode *inode, *dir; + unsigned fpos; + int ret; + + _enter("{sb=%p n=%s},", dentry->d_sb, dentry->d_name.name); + + /* lock down the parent dentry so we can peer at it */ + parent = dget_parent(dentry->d_parent); + + dir = parent->d_inode; + inode = dentry->d_inode; + + /* handle a negative dentry */ + if (!inode) + goto out_bad; + + /* handle a bad inode */ + if (is_bad_inode(inode)) { + printk("kAFS: afs_d_revalidate: %s/%s has bad inode\n", + dentry->d_parent->d_name.name, dentry->d_name.name); + goto out_bad; + } + + /* force a full look up if the parent directory changed since last the + * server was consulted + * - otherwise this inode must still exist, even if the inode details + * themselves have changed + */ + if (AFS_FS_I(dir)->flags & AFS_VNODE_CHANGED) + afs_vnode_fetch_status(AFS_FS_I(dir)); + + if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) { + _debug("%s: parent dir deleted", dentry->d_name.name); + goto out_bad; + } + + if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED) { + _debug("%s: file already deleted", dentry->d_name.name); + goto out_bad; + } + + if ((unsigned long) dentry->d_fsdata != + (unsigned long) AFS_FS_I(dir)->status.version) { + _debug("%s: parent changed %lu -> %u", + dentry->d_name.name, + (unsigned long) dentry->d_fsdata, + (unsigned) AFS_FS_I(dir)->status.version); + + /* search the directory for this vnode */ + cookie.name = dentry->d_name.name; + cookie.nlen = dentry->d_name.len; + cookie.fid.vid = AFS_FS_I(inode)->volume->vid; + cookie.found = 0; + + fpos = 0; + ret = afs_dir_iterate(dir, &fpos, &cookie, + afs_dir_lookup_filldir); + if (ret < 0) { + _debug("failed to iterate dir %s: %d", + parent->d_name.name, ret); + goto out_bad; + } + + if (!cookie.found) { + _debug("%s: dirent not found", dentry->d_name.name); + goto not_found; + } + + /* if the vnode ID has changed, then the dirent points to a + * different file */ + if (cookie.fid.vnode != AFS_FS_I(inode)->fid.vnode) { + _debug("%s: dirent changed", dentry->d_name.name); + goto not_found; + } + + /* if the vnode ID uniqifier has changed, then the file has + * been deleted */ + if (cookie.fid.unique != AFS_FS_I(inode)->fid.unique) { + _debug("%s: file deleted (uq %u -> %u I:%lu)", + dentry->d_name.name, + cookie.fid.unique, + AFS_FS_I(inode)->fid.unique, + inode->i_version); + spin_lock(&AFS_FS_I(inode)->lock); + AFS_FS_I(inode)->flags |= AFS_VNODE_DELETED; + spin_unlock(&AFS_FS_I(inode)->lock); + invalidate_remote_inode(inode); + goto out_bad; + } + + dentry->d_fsdata = + (void *) (unsigned long) AFS_FS_I(dir)->status.version; + } + + out_valid: + dput(parent); + _leave(" = 1 [valid]"); + return 1; + + /* the dirent, if it exists, now points to a different vnode */ + not_found: + spin_lock(&dentry->d_lock); + dentry->d_flags |= DCACHE_NFSFS_RENAMED; + spin_unlock(&dentry->d_lock); + + out_bad: + if (inode) { + /* don't unhash if we have submounts */ + if (have_submounts(dentry)) + goto out_valid; + } + + shrink_dcache_parent(dentry); + + _debug("dropping dentry %s/%s", + dentry->d_parent->d_name.name, dentry->d_name.name); + d_drop(dentry); + + dput(parent); + + _leave(" = 0 [bad]"); + return 0; +} /* end afs_d_revalidate() */ + +/*****************************************************************************/ +/* + * allow the VFS to enquire as to whether a dentry should be unhashed (mustn't + * sleep) + * - called from dput() when d_count is going to 0. + * - return 1 to request dentry be unhashed, 0 otherwise + */ +static int afs_d_delete(struct dentry *dentry) +{ + _enter("%s", dentry->d_name.name); + + if (dentry->d_flags & DCACHE_NFSFS_RENAMED) + goto zap; + + if (dentry->d_inode) { + if (AFS_FS_I(dentry->d_inode)->flags & AFS_VNODE_DELETED) + goto zap; + } + + _leave(" = 0 [keep]"); + return 0; + + zap: + _leave(" = 1 [zap]"); + return 1; +} /* end afs_d_delete() */ diff --git a/fs/afs/errors.h b/fs/afs/errors.h new file mode 100644 index 00000000000..574d94ac8d0 --- /dev/null +++ b/fs/afs/errors.h @@ -0,0 +1,34 @@ +/* errors.h: AFS abort/error codes + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_ERRORS_H +#define _LINUX_AFS_ERRORS_H + +#include "types.h" + +/* file server abort codes */ +typedef enum { + VSALVAGE = 101, /* volume needs salvaging */ + VNOVNODE = 102, /* no such file/dir (vnode) */ + VNOVOL = 103, /* no such volume or volume unavailable */ + VVOLEXISTS = 104, /* volume name already exists */ + VNOSERVICE = 105, /* volume not currently in service */ + VOFFLINE = 106, /* volume is currently offline (more info available [VVL-spec]) */ + VONLINE = 107, /* volume is already online */ + VDISKFULL = 108, /* disk partition is full */ + VOVERQUOTA = 109, /* volume's maximum quota exceeded */ + VBUSY = 110, /* volume is temporarily unavailable */ + VMOVED = 111, /* volume moved to new server - ask this FS where */ +} afs_rxfs_abort_t; + +extern int afs_abort_to_error(int abortcode); + +#endif /* _LINUX_AFS_ERRORS_H */ diff --git a/fs/afs/file.c b/fs/afs/file.c new file mode 100644 index 00000000000..6b6bb7c8abf --- /dev/null +++ b/fs/afs/file.c @@ -0,0 +1,305 @@ +/* file.c: AFS filesystem file handling + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include <linux/buffer_head.h> +#include "volume.h" +#include "vnode.h" +#include <rxrpc/call.h> +#include "internal.h" + +#if 0 +static int afs_file_open(struct inode *inode, struct file *file); +static int afs_file_release(struct inode *inode, struct file *file); +#endif + +static int afs_file_readpage(struct file *file, struct page *page); +static int afs_file_invalidatepage(struct page *page, unsigned long offset); +static int afs_file_releasepage(struct page *page, int gfp_flags); + +static ssize_t afs_file_write(struct file *file, const char __user *buf, + size_t size, loff_t *off); + +struct inode_operations afs_file_inode_operations = { + .getattr = afs_inode_getattr, +}; + +struct file_operations afs_file_file_operations = { + .read = generic_file_read, + .write = afs_file_write, + .mmap = generic_file_mmap, +#if 0 + .open = afs_file_open, + .release = afs_file_release, + .fsync = afs_file_fsync, +#endif +}; + +struct address_space_operations afs_fs_aops = { + .readpage = afs_file_readpage, + .sync_page = block_sync_page, + .set_page_dirty = __set_page_dirty_nobuffers, + .releasepage = afs_file_releasepage, + .invalidatepage = afs_file_invalidatepage, +}; + +/*****************************************************************************/ +/* + * AFS file write + */ +static ssize_t afs_file_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct afs_vnode *vnode; + + vnode = AFS_FS_I(file->f_dentry->d_inode); + if (vnode->flags & AFS_VNODE_DELETED) + return -ESTALE; + + return -EIO; +} /* end afs_file_write() */ + +/*****************************************************************************/ +/* + * deal with notification that a page was read from the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_file_readpage_read_complete(void *cookie_data, + struct page *page, + void *data, + int error) +{ + _enter("%p,%p,%p,%d", cookie_data, page, data, error); + + if (error) + SetPageError(page); + else + SetPageUptodate(page); + unlock_page(page); + +} /* end afs_file_readpage_read_complete() */ +#endif + +/*****************************************************************************/ +/* + * deal with notification that a page was written to the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_file_readpage_write_complete(void *cookie_data, + struct page *page, + void *data, + int error) +{ + _enter("%p,%p,%p,%d", cookie_data, page, data, error); + + unlock_page(page); + +} /* end afs_file_readpage_write_complete() */ +#endif + +/*****************************************************************************/ +/* + * AFS read page from file (or symlink) + */ +static int afs_file_readpage(struct file *file, struct page *page) +{ + struct afs_rxfs_fetch_descriptor desc; +#ifdef AFS_CACHING_SUPPORT + struct cachefs_page *pageio; +#endif + struct afs_vnode *vnode; + struct inode *inode; + int ret; + + inode = page->mapping->host; + + _enter("{%lu},{%lu}", inode->i_ino, page->index); + + vnode = AFS_FS_I(inode); + + if (!PageLocked(page)) + PAGE_BUG(page); + + ret = -ESTALE; + if (vnode->flags & AFS_VNODE_DELETED) + goto error; + +#ifdef AFS_CACHING_SUPPORT + ret = cachefs_page_get_private(page, &pageio, GFP_NOIO); + if (ret < 0) + goto error; + + /* is it cached? */ + ret = cachefs_read_or_alloc_page(vnode->cache, + page, + afs_file_readpage_read_complete, + NULL, + GFP_KERNEL); +#else + ret = -ENOBUFS; +#endif + + switch (ret) { + /* read BIO submitted and wb-journal entry found */ + case 1: + BUG(); // TODO - handle wb-journal match + + /* read BIO submitted (page in cache) */ + case 0: + break; + + /* no page available in cache */ + case -ENOBUFS: + case -ENODATA: + default: + desc.fid = vnode->fid; + desc.offset = page->index << PAGE_CACHE_SHIFT; + desc.size = min((size_t) (inode->i_size - desc.offset), + (size_t) PAGE_SIZE); + desc.buffer = kmap(page); + + clear_page(desc.buffer); + + /* read the contents of the file from the server into the + * page */ + ret = afs_vnode_fetch_data(vnode, &desc); + kunmap(page); + if (ret < 0) { + if (ret==-ENOENT) { + _debug("got NOENT from server" + " - marking file deleted and stale"); + vnode->flags |= AFS_VNODE_DELETED; + ret = -ESTALE; + } + +#ifdef AFS_CACHING_SUPPORT + cachefs_uncache_page(vnode->cache, page); +#endif + goto error; + } + + SetPageUptodate(page); + +#ifdef AFS_CACHING_SUPPORT + if (cachefs_write_page(vnode->cache, + page, + afs_file_readpage_write_complete, + NULL, + GFP_KERNEL) != 0 + ) { + cachefs_uncache_page(vnode->cache, page); + unlock_page(page); + } +#else + unlock_page(page); +#endif + } + + _leave(" = 0"); + return 0; + + error: + SetPageError(page); + unlock_page(page); + + _leave(" = %d", ret); + return ret; + +} /* end afs_file_readpage() */ + +/*****************************************************************************/ +/* + * get a page cookie for the specified page + */ +#ifdef AFS_CACHING_SUPPORT +int afs_cache_get_page_cookie(struct page *page, + struct cachefs_page **_page_cookie) +{ + int ret; + + _enter(""); + ret = cachefs_page_get_private(page,_page_cookie, GFP_NOIO); + + _leave(" = %d", ret); + return ret; +} /* end afs_cache_get_page_cookie() */ +#endif + +/*****************************************************************************/ +/* + * invalidate part or all of a page + */ +static int afs_file_invalidatepage(struct page *page, unsigned long offset) +{ + int ret = 1; + + _enter("{%lu},%lu", page->index, offset); + + BUG_ON(!PageLocked(page)); + + if (PagePrivate(page)) { +#ifdef AFS_CACHING_SUPPORT + struct afs_vnode *vnode = AFS_FS_I(page->mapping->host); + cachefs_uncache_page(vnode->cache,page); +#endif + + /* We release buffers only if the entire page is being + * invalidated. + * The get_block cached value has been unconditionally + * invalidated, so real IO is not possible anymore. + */ + if (offset == 0) { + BUG_ON(!PageLocked(page)); + + ret = 0; + if (!PageWriteback(page)) + ret = page->mapping->a_ops->releasepage(page, + 0); + } + } + + _leave(" = %d", ret); + return ret; +} /* end afs_file_invalidatepage() */ + +/*****************************************************************************/ +/* + * release a page and cleanup its private data + */ +static int afs_file_releasepage(struct page *page, int gfp_flags) +{ + struct cachefs_page *pageio; + + _enter("{%lu},%x", page->index, gfp_flags); + + if (PagePrivate(page)) { +#ifdef AFS_CACHING_SUPPORT + struct afs_vnode *vnode = AFS_FS_I(page->mapping->host); + cachefs_uncache_page(vnode->cache, page); +#endif + + pageio = (struct cachefs_page *) page->private; + page->private = 0; + ClearPagePrivate(page); + + if (pageio) + kfree(pageio); + } + + _leave(" = 0"); + return 0; +} /* end afs_file_releasepage() */ diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c new file mode 100644 index 00000000000..61bc371532a --- /dev/null +++ b/fs/afs/fsclient.c @@ -0,0 +1,837 @@ +/* fsclient.c: AFS File Server client stubs + * + * Copyright (C) 2002 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/init.h> +#include <linux/sched.h> +#include <rxrpc/rxrpc.h> +#include <rxrpc/transport.h> +#include <rxrpc/connection.h> +#include <rxrpc/call.h> +#include "fsclient.h" +#include "cmservice.h" +#include "vnode.h" +#include "server.h" +#include "errors.h" +#include "internal.h" + +#define FSFETCHSTATUS 132 /* AFS Fetch file status */ +#define FSFETCHDATA 130 /* AFS Fetch file data */ +#define FSGIVEUPCALLBACKS 147 /* AFS Discard callback promises */ +#define FSGETVOLUMEINFO 148 /* AFS Get root volume information */ +#define FSGETROOTVOLUME 151 /* AFS Get root volume name */ +#define FSLOOKUP 161 /* AFS lookup file in directory */ + +/*****************************************************************************/ +/* + * map afs abort codes to/from Linux error codes + * - called with call->lock held + */ +static void afs_rxfs_aemap(struct rxrpc_call *call) +{ + switch (call->app_err_state) { + case RXRPC_ESTATE_LOCAL_ABORT: + call->app_abort_code = -call->app_errno; + break; + case RXRPC_ESTATE_PEER_ABORT: + call->app_errno = afs_abort_to_error(call->app_abort_code); + break; + default: + break; + } +} /* end afs_rxfs_aemap() */ + +/*****************************************************************************/ +/* + * get the root volume name from a fileserver + * - this operation doesn't seem to work correctly in OpenAFS server 1.2.2 + */ +#if 0 +int afs_rxfs_get_root_volume(struct afs_server *server, + char *buf, size_t *buflen) +{ + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[2]; + size_t sent; + int ret; + u32 param[1]; + + DECLARE_WAITQUEUE(myself, current); + + kenter("%p,%p,%u",server, buf, *buflen); + + /* get hold of the fileserver connection */ + ret = afs_server_get_fsconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSGETROOTVOLUME; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + param[0] = htonl(FSGETROOTVOLUME); + + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY || + signal_pending(current)) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + + ret = -EINTR; + if (signal_pending(current)) + goto abort; + + switch (call->app_call_state) { + case RXRPC_CSTATE_ERROR: + ret = call->app_errno; + kdebug("Got Error: %d", ret); + goto out_unwait; + + case RXRPC_CSTATE_CLNT_GOT_REPLY: + /* read the reply */ + kdebug("Got Reply: qty=%d", call->app_ready_qty); + + ret = -EBADMSG; + if (call->app_ready_qty <= 4) + goto abort; + + ret = rxrpc_call_read_data(call, NULL, call->app_ready_qty, 0); + if (ret < 0) + goto abort; + +#if 0 + /* unmarshall the reply */ + bp = buffer; + for (loop = 0; loop < 65; loop++) + entry->name[loop] = ntohl(*bp++); + entry->name[64] = 0; + + entry->type = ntohl(*bp++); + entry->num_servers = ntohl(*bp++); + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].addr.s_addr = *bp++; + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].partition = ntohl(*bp++); + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].flags = ntohl(*bp++); + + for (loop = 0; loop < 3; loop++) + entry->volume_ids[loop] = ntohl(*bp++); + + entry->clone_id = ntohl(*bp++); + entry->flags = ntohl(*bp); +#endif + + /* success */ + ret = 0; + goto out_unwait; + + default: + BUG(); + } + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_fsconn(server, conn); + out: + kleave(""); + return ret; +} /* end afs_rxfs_get_root_volume() */ +#endif + +/*****************************************************************************/ +/* + * get information about a volume + */ +#if 0 +int afs_rxfs_get_volume_info(struct afs_server *server, + const char *name, + struct afs_volume_info *vinfo) +{ + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[3]; + size_t sent; + int ret; + u32 param[2], *bp, zero; + + DECLARE_WAITQUEUE(myself, current); + + _enter("%p,%s,%p", server, name, vinfo); + + /* get hold of the fileserver connection */ + ret = afs_server_get_fsconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSGETVOLUMEINFO; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + piov[1].iov_len = strlen(name); + piov[1].iov_base = (char *) name; + + zero = 0; + piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3; + piov[2].iov_base = &zero; + + param[0] = htonl(FSGETVOLUMEINFO); + param[1] = htonl(piov[1].iov_len); + + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + bp = rxrpc_call_alloc_scratch(call, 64); + + ret = rxrpc_call_read_data(call, bp, 64, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) { + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + goto abort; + } + + /* unmarshall the reply */ + vinfo->vid = ntohl(*bp++); + vinfo->type = ntohl(*bp++); + + vinfo->type_vids[0] = ntohl(*bp++); + vinfo->type_vids[1] = ntohl(*bp++); + vinfo->type_vids[2] = ntohl(*bp++); + vinfo->type_vids[3] = ntohl(*bp++); + vinfo->type_vids[4] = ntohl(*bp++); + + vinfo->nservers = ntohl(*bp++); + vinfo->servers[0].addr.s_addr = *bp++; + vinfo->servers[1].addr.s_addr = *bp++; + vinfo->servers[2].addr.s_addr = *bp++; + vinfo->servers[3].addr.s_addr = *bp++; + vinfo->servers[4].addr.s_addr = *bp++; + vinfo->servers[5].addr.s_addr = *bp++; + vinfo->servers[6].addr.s_addr = *bp++; + vinfo->servers[7].addr.s_addr = *bp++; + + ret = -EBADMSG; + if (vinfo->nservers > 8) + goto abort; + + /* success */ + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_fsconn(server, conn); + out: + _leave(""); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; + +} /* end afs_rxfs_get_volume_info() */ +#endif + +/*****************************************************************************/ +/* + * fetch the status information for a file + */ +int afs_rxfs_fetch_file_status(struct afs_server *server, + struct afs_vnode *vnode, + struct afs_volsync *volsync) +{ + struct afs_server_callslot callslot; + struct rxrpc_call *call; + struct kvec piov[1]; + size_t sent; + int ret; + __be32 *bp; + + DECLARE_WAITQUEUE(myself, current); + + _enter("%p,{%u,%u,%u}", + server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique); + + /* get hold of the fileserver connection */ + ret = afs_server_request_callslot(server, &callslot); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, + &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSFETCHSTATUS; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + bp = rxrpc_call_alloc_scratch(call, 16); + bp[0] = htonl(FSFETCHSTATUS); + bp[1] = htonl(vnode->fid.vid); + bp[2] = htonl(vnode->fid.vnode); + bp[3] = htonl(vnode->fid.unique); + + piov[0].iov_len = 16; + piov[0].iov_base = bp; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + bp = rxrpc_call_alloc_scratch(call, 120); + + ret = rxrpc_call_read_data(call, bp, 120, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) { + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + goto abort; + } + + /* unmarshall the reply */ + vnode->status.if_version = ntohl(*bp++); + vnode->status.type = ntohl(*bp++); + vnode->status.nlink = ntohl(*bp++); + vnode->status.size = ntohl(*bp++); + vnode->status.version = ntohl(*bp++); + vnode->status.author = ntohl(*bp++); + vnode->status.owner = ntohl(*bp++); + vnode->status.caller_access = ntohl(*bp++); + vnode->status.anon_access = ntohl(*bp++); + vnode->status.mode = ntohl(*bp++); + vnode->status.parent.vid = vnode->fid.vid; + vnode->status.parent.vnode = ntohl(*bp++); + vnode->status.parent.unique = ntohl(*bp++); + bp++; /* seg size */ + vnode->status.mtime_client = ntohl(*bp++); + vnode->status.mtime_server = ntohl(*bp++); + bp++; /* group */ + bp++; /* sync counter */ + vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32; + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + + vnode->cb_version = ntohl(*bp++); + vnode->cb_expiry = ntohl(*bp++); + vnode->cb_type = ntohl(*bp++); + + if (volsync) { + volsync->creation = ntohl(*bp++); + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + bp++; /* spare5 */ + bp++; /* spare6 */ + } + + /* success */ + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_callslot(server, &callslot); + out: + _leave(""); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; +} /* end afs_rxfs_fetch_file_status() */ + +/*****************************************************************************/ +/* + * fetch the contents of a file or directory + */ +int afs_rxfs_fetch_file_data(struct afs_server *server, + struct afs_vnode *vnode, + struct afs_rxfs_fetch_descriptor *desc, + struct afs_volsync *volsync) +{ + struct afs_server_callslot callslot; + struct rxrpc_call *call; + struct kvec piov[1]; + size_t sent; + int ret; + __be32 *bp; + + DECLARE_WAITQUEUE(myself, current); + + _enter("%p,{fid={%u,%u,%u},sz=%Zu,of=%lu}", + server, + desc->fid.vid, + desc->fid.vnode, + desc->fid.unique, + desc->size, + desc->offset); + + /* get hold of the fileserver connection */ + ret = afs_server_request_callslot(server, &callslot); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSFETCHDATA; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + bp = rxrpc_call_alloc_scratch(call, 24); + bp[0] = htonl(FSFETCHDATA); + bp[1] = htonl(desc->fid.vid); + bp[2] = htonl(desc->fid.vnode); + bp[3] = htonl(desc->fid.unique); + bp[4] = htonl(desc->offset); + bp[5] = htonl(desc->size); + + piov[0].iov_len = 24; + piov[0].iov_base = bp; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the data count to arrive */ + ret = rxrpc_call_read_data(call, bp, 4, RXRPC_CALL_READ_BLOCK); + if (ret < 0) + goto read_failed; + + desc->actual = ntohl(bp[0]); + if (desc->actual != desc->size) { + ret = -EBADMSG; + goto abort; + } + + /* call the app to read the actual data */ + rxrpc_call_reset_scratch(call); + + ret = rxrpc_call_read_data(call, desc->buffer, desc->actual, + RXRPC_CALL_READ_BLOCK); + if (ret < 0) + goto read_failed; + + /* wait for the rest of the reply to completely arrive */ + rxrpc_call_reset_scratch(call); + bp = rxrpc_call_alloc_scratch(call, 120); + + ret = rxrpc_call_read_data(call, bp, 120, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) + goto read_failed; + + /* unmarshall the reply */ + vnode->status.if_version = ntohl(*bp++); + vnode->status.type = ntohl(*bp++); + vnode->status.nlink = ntohl(*bp++); + vnode->status.size = ntohl(*bp++); + vnode->status.version = ntohl(*bp++); + vnode->status.author = ntohl(*bp++); + vnode->status.owner = ntohl(*bp++); + vnode->status.caller_access = ntohl(*bp++); + vnode->status.anon_access = ntohl(*bp++); + vnode->status.mode = ntohl(*bp++); + vnode->status.parent.vid = desc->fid.vid; + vnode->status.parent.vnode = ntohl(*bp++); + vnode->status.parent.unique = ntohl(*bp++); + bp++; /* seg size */ + vnode->status.mtime_client = ntohl(*bp++); + vnode->status.mtime_server = ntohl(*bp++); + bp++; /* group */ + bp++; /* sync counter */ + vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32; + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + + vnode->cb_version = ntohl(*bp++); + vnode->cb_expiry = ntohl(*bp++); + vnode->cb_type = ntohl(*bp++); + + if (volsync) { + volsync->creation = ntohl(*bp++); + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + bp++; /* spare5 */ + bp++; /* spare6 */ + } + + /* success */ + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq,&myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_callslot(server, &callslot); + out: + _leave(" = %d", ret); + return ret; + + read_failed: + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; + +} /* end afs_rxfs_fetch_file_data() */ + +/*****************************************************************************/ +/* + * ask the AFS fileserver to discard a callback request on a file + */ +int afs_rxfs_give_up_callback(struct afs_server *server, + struct afs_vnode *vnode) +{ + struct afs_server_callslot callslot; + struct rxrpc_call *call; + struct kvec piov[1]; + size_t sent; + int ret; + __be32 *bp; + + DECLARE_WAITQUEUE(myself, current); + + _enter("%p,{%u,%u,%u}", + server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique); + + /* get hold of the fileserver connection */ + ret = afs_server_request_callslot(server, &callslot); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSGIVEUPCALLBACKS; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + bp = rxrpc_call_alloc_scratch(call, (1 + 4 + 4) * 4); + + piov[0].iov_len = (1 + 4 + 4) * 4; + piov[0].iov_base = bp; + + *bp++ = htonl(FSGIVEUPCALLBACKS); + *bp++ = htonl(1); + *bp++ = htonl(vnode->fid.vid); + *bp++ = htonl(vnode->fid.vnode); + *bp++ = htonl(vnode->fid.unique); + *bp++ = htonl(1); + *bp++ = htonl(vnode->cb_version); + *bp++ = htonl(vnode->cb_expiry); + *bp++ = htonl(vnode->cb_type); + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY || + signal_pending(current)) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + + ret = -EINTR; + if (signal_pending(current)) + goto abort; + + switch (call->app_call_state) { + case RXRPC_CSTATE_ERROR: + ret = call->app_errno; + goto out_unwait; + + case RXRPC_CSTATE_CLNT_GOT_REPLY: + ret = 0; + goto out_unwait; + + default: + BUG(); + } + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_callslot(server, &callslot); + out: + _leave(""); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; +} /* end afs_rxfs_give_up_callback() */ + +/*****************************************************************************/ +/* + * look a filename up in a directory + * - this operation doesn't seem to work correctly in OpenAFS server 1.2.2 + */ +#if 0 +int afs_rxfs_lookup(struct afs_server *server, + struct afs_vnode *dir, + const char *filename, + struct afs_vnode *vnode, + struct afs_volsync *volsync) +{ + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[3]; + size_t sent; + int ret; + u32 *bp, zero; + + DECLARE_WAITQUEUE(myself, current); + + kenter("%p,{%u,%u,%u},%s", + server, fid->vid, fid->vnode, fid->unique, filename); + + /* get hold of the fileserver connection */ + ret = afs_server_get_fsconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = FSLOOKUP; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq,&myself); + + /* marshall the parameters */ + bp = rxrpc_call_alloc_scratch(call, 20); + + zero = 0; + + piov[0].iov_len = 20; + piov[0].iov_base = bp; + piov[1].iov_len = strlen(filename); + piov[1].iov_base = (char *) filename; + piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3; + piov[2].iov_base = &zero; + + *bp++ = htonl(FSLOOKUP); + *bp++ = htonl(dirfid->vid); + *bp++ = htonl(dirfid->vnode); + *bp++ = htonl(dirfid->unique); + *bp++ = htonl(piov[1].iov_len); + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + bp = rxrpc_call_alloc_scratch(call, 220); + + ret = rxrpc_call_read_data(call, bp, 220, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) { + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + goto abort; + } + + /* unmarshall the reply */ + fid->vid = ntohl(*bp++); + fid->vnode = ntohl(*bp++); + fid->unique = ntohl(*bp++); + + vnode->status.if_version = ntohl(*bp++); + vnode->status.type = ntohl(*bp++); + vnode->status.nlink = ntohl(*bp++); + vnode->status.size = ntohl(*bp++); + vnode->status.version = ntohl(*bp++); + vnode->status.author = ntohl(*bp++); + vnode->status.owner = ntohl(*bp++); + vnode->status.caller_access = ntohl(*bp++); + vnode->status.anon_access = ntohl(*bp++); + vnode->status.mode = ntohl(*bp++); + vnode->status.parent.vid = dirfid->vid; + vnode->status.parent.vnode = ntohl(*bp++); + vnode->status.parent.unique = ntohl(*bp++); + bp++; /* seg size */ + vnode->status.mtime_client = ntohl(*bp++); + vnode->status.mtime_server = ntohl(*bp++); + bp++; /* group */ + bp++; /* sync counter */ + vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32; + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + + dir->status.if_version = ntohl(*bp++); + dir->status.type = ntohl(*bp++); + dir->status.nlink = ntohl(*bp++); + dir->status.size = ntohl(*bp++); + dir->status.version = ntohl(*bp++); + dir->status.author = ntohl(*bp++); + dir->status.owner = ntohl(*bp++); + dir->status.caller_access = ntohl(*bp++); + dir->status.anon_access = ntohl(*bp++); + dir->status.mode = ntohl(*bp++); + dir->status.parent.vid = dirfid->vid; + dir->status.parent.vnode = ntohl(*bp++); + dir->status.parent.unique = ntohl(*bp++); + bp++; /* seg size */ + dir->status.mtime_client = ntohl(*bp++); + dir->status.mtime_server = ntohl(*bp++); + bp++; /* group */ + bp++; /* sync counter */ + dir->status.version |= ((unsigned long long) ntohl(*bp++)) << 32; + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + + callback->fid = *fid; + callback->version = ntohl(*bp++); + callback->expiry = ntohl(*bp++); + callback->type = ntohl(*bp++); + + if (volsync) { + volsync->creation = ntohl(*bp++); + bp++; /* spare2 */ + bp++; /* spare3 */ + bp++; /* spare4 */ + bp++; /* spare5 */ + bp++; /* spare6 */ + } + + /* success */ + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + afs_server_release_fsconn(server, conn); + out: + kleave(""); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; +} /* end afs_rxfs_lookup() */ +#endif diff --git a/fs/afs/fsclient.h b/fs/afs/fsclient.h new file mode 100644 index 00000000000..8ba3e749ee3 --- /dev/null +++ b/fs/afs/fsclient.h @@ -0,0 +1,54 @@ +/* fsclient.h: AFS File Server client stub declarations + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_FSCLIENT_H +#define _LINUX_AFS_FSCLIENT_H + +#include "server.h" + +extern int afs_rxfs_get_volume_info(struct afs_server *server, + const char *name, + struct afs_volume_info *vinfo); + +extern int afs_rxfs_fetch_file_status(struct afs_server *server, + struct afs_vnode *vnode, + struct afs_volsync *volsync); + +struct afs_rxfs_fetch_descriptor { + struct afs_fid fid; /* file ID to fetch */ + size_t size; /* total number of bytes to fetch */ + off_t offset; /* offset in file to start from */ + void *buffer; /* read buffer */ + size_t actual; /* actual size sent back by server */ +}; + +extern int afs_rxfs_fetch_file_data(struct afs_server *server, + struct afs_vnode *vnode, + struct afs_rxfs_fetch_descriptor *desc, + struct afs_volsync *volsync); + +extern int afs_rxfs_give_up_callback(struct afs_server *server, + struct afs_vnode *vnode); + +/* this doesn't appear to work in OpenAFS server */ +extern int afs_rxfs_lookup(struct afs_server *server, + struct afs_vnode *dir, + const char *filename, + struct afs_vnode *vnode, + struct afs_volsync *volsync); + +/* this is apparently mis-implemented in OpenAFS server */ +extern int afs_rxfs_get_root_volume(struct afs_server *server, + char *buf, + size_t *buflen); + + +#endif /* _LINUX_AFS_FSCLIENT_H */ diff --git a/fs/afs/inode.c b/fs/afs/inode.c new file mode 100644 index 00000000000..c476fde33fb --- /dev/null +++ b/fs/afs/inode.c @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2002 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@cambridge.redhat.com> + * David Howells <dhowells@redhat.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include "volume.h" +#include "vnode.h" +#include "super.h" +#include "internal.h" + +struct afs_iget_data { + struct afs_fid fid; + struct afs_volume *volume; /* volume on which resides */ +}; + +/*****************************************************************************/ +/* + * map the AFS file status to the inode member variables + */ +static int afs_inode_map_status(struct afs_vnode *vnode) +{ + struct inode *inode = AFS_VNODE_TO_I(vnode); + + _debug("FS: ft=%d lk=%d sz=%Zu ver=%Lu mod=%hu", + vnode->status.type, + vnode->status.nlink, + vnode->status.size, + vnode->status.version, + vnode->status.mode); + + switch (vnode->status.type) { + case AFS_FTYPE_FILE: + inode->i_mode = S_IFREG | vnode->status.mode; + inode->i_op = &afs_file_inode_operations; + inode->i_fop = &afs_file_file_operations; + break; + case AFS_FTYPE_DIR: + inode->i_mode = S_IFDIR | vnode->status.mode; + inode->i_op = &afs_dir_inode_operations; + inode->i_fop = &afs_dir_file_operations; + break; + case AFS_FTYPE_SYMLINK: + inode->i_mode = S_IFLNK | vnode->status.mode; + inode->i_op = &page_symlink_inode_operations; + break; + default: + printk("kAFS: AFS vnode with undefined type\n"); + return -EBADMSG; + } + + inode->i_nlink = vnode->status.nlink; + inode->i_uid = vnode->status.owner; + inode->i_gid = 0; + inode->i_size = vnode->status.size; + inode->i_ctime.tv_sec = vnode->status.mtime_server; + inode->i_ctime.tv_nsec = 0; + inode->i_atime = inode->i_mtime = inode->i_ctime; + inode->i_blksize = PAGE_CACHE_SIZE; + inode->i_blocks = 0; + inode->i_version = vnode->fid.unique; + inode->i_mapping->a_ops = &afs_fs_aops; + + /* check to see whether a symbolic link is really a mountpoint */ + if (vnode->status.type == AFS_FTYPE_SYMLINK) { + afs_mntpt_check_symlink(vnode); + + if (vnode->flags & AFS_VNODE_MOUNTPOINT) { + inode->i_mode = S_IFDIR | vnode->status.mode; + inode->i_op = &afs_mntpt_inode_operations; + inode->i_fop = &afs_mntpt_file_operations; + } + } + + return 0; +} /* end afs_inode_map_status() */ + +/*****************************************************************************/ +/* + * attempt to fetch the status of an inode, coelescing multiple simultaneous + * fetches + */ +static int afs_inode_fetch_status(struct inode *inode) +{ + struct afs_vnode *vnode; + int ret; + + vnode = AFS_FS_I(inode); + + ret = afs_vnode_fetch_status(vnode); + + if (ret == 0) + ret = afs_inode_map_status(vnode); + + return ret; + +} /* end afs_inode_fetch_status() */ + +/*****************************************************************************/ +/* + * iget5() comparator + */ +static int afs_iget5_test(struct inode *inode, void *opaque) +{ + struct afs_iget_data *data = opaque; + + return inode->i_ino == data->fid.vnode && + inode->i_version == data->fid.unique; +} /* end afs_iget5_test() */ + +/*****************************************************************************/ +/* + * iget5() inode initialiser + */ +static int afs_iget5_set(struct inode *inode, void *opaque) +{ + struct afs_iget_data *data = opaque; + struct afs_vnode *vnode = AFS_FS_I(inode); + + inode->i_ino = data->fid.vnode; + inode->i_version = data->fid.unique; + vnode->fid = data->fid; + vnode->volume = data->volume; + + return 0; +} /* end afs_iget5_set() */ + +/*****************************************************************************/ +/* + * inode retrieval + */ +inline int afs_iget(struct super_block *sb, struct afs_fid *fid, + struct inode **_inode) +{ + struct afs_iget_data data = { .fid = *fid }; + struct afs_super_info *as; + struct afs_vnode *vnode; + struct inode *inode; + int ret; + + _enter(",{%u,%u,%u},,", fid->vid, fid->vnode, fid->unique); + + as = sb->s_fs_info; + data.volume = as->volume; + + inode = iget5_locked(sb, fid->vnode, afs_iget5_test, afs_iget5_set, + &data); + if (!inode) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + vnode = AFS_FS_I(inode); + + /* deal with an existing inode */ + if (!(inode->i_state & I_NEW)) { + ret = afs_vnode_fetch_status(vnode); + if (ret==0) + *_inode = inode; + else + iput(inode); + _leave(" = %d", ret); + return ret; + } + +#ifdef AFS_CACHING_SUPPORT + /* set up caching before reading the status, as fetch-status reads the + * first page of symlinks to see if they're really mntpts */ + cachefs_acquire_cookie(vnode->volume->cache, + NULL, + vnode, + &vnode->cache); +#endif + + /* okay... it's a new inode */ + inode->i_flags |= S_NOATIME; + vnode->flags |= AFS_VNODE_CHANGED; + ret = afs_inode_fetch_status(inode); + if (ret<0) + goto bad_inode; + + /* success */ + unlock_new_inode(inode); + + *_inode = inode; + _leave(" = 0 [CB { v=%u x=%lu t=%u }]", + vnode->cb_version, + vnode->cb_timeout.timo_jif, + vnode->cb_type); + return 0; + + /* failure */ + bad_inode: + make_bad_inode(inode); + unlock_new_inode(inode); + iput(inode); + + _leave(" = %d [bad]", ret); + return ret; +} /* end afs_iget() */ + +/*****************************************************************************/ +/* + * read the attributes of an inode + */ +int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat) +{ + struct afs_vnode *vnode; + struct inode *inode; + int ret; + + inode = dentry->d_inode; + + _enter("{ ino=%lu v=%lu }", inode->i_ino, inode->i_version); + + vnode = AFS_FS_I(inode); + + ret = afs_inode_fetch_status(inode); + if (ret == -ENOENT) { + _leave(" = %d [%d %p]", + ret, atomic_read(&dentry->d_count), dentry->d_inode); + return ret; + } + else if (ret < 0) { + make_bad_inode(inode); + _leave(" = %d", ret); + return ret; + } + + /* transfer attributes from the inode structure to the stat + * structure */ + generic_fillattr(inode, stat); + + _leave(" = 0 CB { v=%u x=%u t=%u }", + vnode->cb_version, + vnode->cb_expiry, + vnode->cb_type); + + return 0; +} /* end afs_inode_getattr() */ + +/*****************************************************************************/ +/* + * clear an AFS inode + */ +void afs_clear_inode(struct inode *inode) +{ + struct afs_vnode *vnode; + + vnode = AFS_FS_I(inode); + + _enter("ino=%lu { vn=%08x v=%u x=%u t=%u }", + inode->i_ino, + vnode->fid.vnode, + vnode->cb_version, + vnode->cb_expiry, + vnode->cb_type + ); + + BUG_ON(inode->i_ino != vnode->fid.vnode); + + afs_vnode_give_up_callback(vnode); + +#ifdef AFS_CACHING_SUPPORT + cachefs_relinquish_cookie(vnode->cache, 0); + vnode->cache = NULL; +#endif + + _leave(""); +} /* end afs_clear_inode() */ diff --git a/fs/afs/internal.h b/fs/afs/internal.h new file mode 100644 index 00000000000..f09860b45c1 --- /dev/null +++ b/fs/afs/internal.h @@ -0,0 +1,140 @@ +/* internal.h: internal AFS stuff + * + * Copyright (C) 2002 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. + */ + +#ifndef AFS_INTERNAL_H +#define AFS_INTERNAL_H + +#include <linux/compiler.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/pagemap.h> + +/* + * debug tracing + */ +#define kenter(FMT, a...) printk("==> %s("FMT")\n",__FUNCTION__ , ## a) +#define kleave(FMT, a...) printk("<== %s()"FMT"\n",__FUNCTION__ , ## a) +#define kdebug(FMT, a...) printk(FMT"\n" , ## a) +#define kproto(FMT, a...) printk("### "FMT"\n" , ## a) +#define knet(FMT, a...) printk(FMT"\n" , ## a) + +#ifdef __KDEBUG +#define _enter(FMT, a...) kenter(FMT , ## a) +#define _leave(FMT, a...) kleave(FMT , ## a) +#define _debug(FMT, a...) kdebug(FMT , ## a) +#define _proto(FMT, a...) kproto(FMT , ## a) +#define _net(FMT, a...) knet(FMT , ## a) +#else +#define _enter(FMT, a...) do { } while(0) +#define _leave(FMT, a...) do { } while(0) +#define _debug(FMT, a...) do { } while(0) +#define _proto(FMT, a...) do { } while(0) +#define _net(FMT, a...) do { } while(0) +#endif + +static inline void afs_discard_my_signals(void) +{ + while (signal_pending(current)) { + siginfo_t sinfo; + + spin_lock_irq(¤t->sighand->siglock); + dequeue_signal(current,¤t->blocked, &sinfo); + spin_unlock_irq(¤t->sighand->siglock); + } +} + +/* + * cell.c + */ +extern struct rw_semaphore afs_proc_cells_sem; +extern struct list_head afs_proc_cells; +#ifdef AFS_CACHING_SUPPORT +extern struct cachefs_index_def afs_cache_cell_index_def; +#endif + +/* + * dir.c + */ +extern struct inode_operations afs_dir_inode_operations; +extern struct file_operations afs_dir_file_operations; + +/* + * file.c + */ +extern struct address_space_operations afs_fs_aops; +extern struct inode_operations afs_file_inode_operations; +extern struct file_operations afs_file_file_operations; + +#ifdef AFS_CACHING_SUPPORT +extern int afs_cache_get_page_cookie(struct page *page, + struct cachefs_page **_page_cookie); +#endif + +/* + * inode.c + */ +extern int afs_iget(struct super_block *sb, struct afs_fid *fid, + struct inode **_inode); +extern int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat); +extern void afs_clear_inode(struct inode *inode); + +/* + * key_afs.c + */ +#ifdef CONFIG_KEYS +extern int afs_key_register(void); +extern void afs_key_unregister(void); +#endif + +/* + * main.c + */ +#ifdef AFS_CACHING_SUPPORT +extern struct cachefs_netfs afs_cache_netfs; +#endif + +/* + * mntpt.c + */ +extern struct inode_operations afs_mntpt_inode_operations; +extern struct file_operations afs_mntpt_file_operations; +extern struct afs_timer afs_mntpt_expiry_timer; +extern struct afs_timer_ops afs_mntpt_expiry_timer_ops; +extern unsigned long afs_mntpt_expiry_timeout; + +extern int afs_mntpt_check_symlink(struct afs_vnode *vnode); + +/* + * super.c + */ +extern int afs_fs_init(void); +extern void afs_fs_exit(void); + +#define AFS_CB_HASH_COUNT (PAGE_SIZE / sizeof(struct list_head)) + +extern struct list_head afs_cb_hash_tbl[]; +extern spinlock_t afs_cb_hash_lock; + +#define afs_cb_hash(SRV,FID) \ + afs_cb_hash_tbl[((unsigned long)(SRV) + \ + (FID)->vid + (FID)->vnode + (FID)->unique) % \ + AFS_CB_HASH_COUNT] + +/* + * proc.c + */ +extern int afs_proc_init(void); +extern void afs_proc_cleanup(void); +extern int afs_proc_cell_setup(struct afs_cell *cell); +extern void afs_proc_cell_remove(struct afs_cell *cell); + +#endif /* AFS_INTERNAL_H */ diff --git a/fs/afs/kafsasyncd.c b/fs/afs/kafsasyncd.c new file mode 100644 index 00000000000..6fc88ae8ad9 --- /dev/null +++ b/fs/afs/kafsasyncd.c @@ -0,0 +1,257 @@ +/* kafsasyncd.c: AFS asynchronous operation daemon + * + * Copyright (C) 2002 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. + * + * + * The AFS async daemon is used to the following: + * - probe "dead" servers to see whether they've come back to life yet. + * - probe "live" servers that we haven't talked to for a while to see if they are better + * candidates for serving than what we're currently using + * - poll volume location servers to keep up to date volume location lists + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include "cell.h" +#include "server.h" +#include "volume.h" +#include "kafsasyncd.h" +#include "kafstimod.h" +#include <rxrpc/call.h> +#include <asm/errno.h> +#include "internal.h" + +static DECLARE_COMPLETION(kafsasyncd_alive); +static DECLARE_COMPLETION(kafsasyncd_dead); +static DECLARE_WAIT_QUEUE_HEAD(kafsasyncd_sleepq); +static struct task_struct *kafsasyncd_task; +static int kafsasyncd_die; + +static int kafsasyncd(void *arg); + +static LIST_HEAD(kafsasyncd_async_attnq); +static LIST_HEAD(kafsasyncd_async_busyq); +static DEFINE_SPINLOCK(kafsasyncd_async_lock); + +static void kafsasyncd_null_call_attn_func(struct rxrpc_call *call) +{ +} + +static void kafsasyncd_null_call_error_func(struct rxrpc_call *call) +{ +} + +/*****************************************************************************/ +/* + * start the async daemon + */ +int afs_kafsasyncd_start(void) +{ + int ret; + + ret = kernel_thread(kafsasyncd, NULL, 0); + if (ret < 0) + return ret; + + wait_for_completion(&kafsasyncd_alive); + + return ret; +} /* end afs_kafsasyncd_start() */ + +/*****************************************************************************/ +/* + * stop the async daemon + */ +void afs_kafsasyncd_stop(void) +{ + /* get rid of my daemon */ + kafsasyncd_die = 1; + wake_up(&kafsasyncd_sleepq); + wait_for_completion(&kafsasyncd_dead); + +} /* end afs_kafsasyncd_stop() */ + +/*****************************************************************************/ +/* + * probing daemon + */ +static int kafsasyncd(void *arg) +{ + struct afs_async_op *op; + int die; + + DECLARE_WAITQUEUE(myself, current); + + kafsasyncd_task = current; + + printk("kAFS: Started kafsasyncd %d\n", current->pid); + + daemonize("kafsasyncd"); + + complete(&kafsasyncd_alive); + + /* loop around looking for things to attend to */ + do { + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&kafsasyncd_sleepq, &myself); + + for (;;) { + if (!list_empty(&kafsasyncd_async_attnq) || + signal_pending(current) || + kafsasyncd_die) + break; + + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + + remove_wait_queue(&kafsasyncd_sleepq, &myself); + set_current_state(TASK_RUNNING); + + try_to_freeze(PF_FREEZE); + + /* discard pending signals */ + afs_discard_my_signals(); + + die = kafsasyncd_die; + + /* deal with the next asynchronous operation requiring + * attention */ + if (!list_empty(&kafsasyncd_async_attnq)) { + struct afs_async_op *op; + + _debug("@@@ Begin Asynchronous Operation"); + + op = NULL; + spin_lock(&kafsasyncd_async_lock); + + if (!list_empty(&kafsasyncd_async_attnq)) { + op = list_entry(kafsasyncd_async_attnq.next, + struct afs_async_op, link); + list_del(&op->link); + list_add_tail(&op->link, + &kafsasyncd_async_busyq); + } + + spin_unlock(&kafsasyncd_async_lock); + + _debug("@@@ Operation %p {%p}\n", + op, op ? op->ops : NULL); + + if (op) + op->ops->attend(op); + + _debug("@@@ End Asynchronous Operation"); + } + + } while(!die); + + /* need to kill all outstanding asynchronous operations before + * exiting */ + kafsasyncd_task = NULL; + spin_lock(&kafsasyncd_async_lock); + + /* fold the busy and attention queues together */ + list_splice_init(&kafsasyncd_async_busyq, + &kafsasyncd_async_attnq); + + /* dequeue kafsasyncd from all their wait queues */ + list_for_each_entry(op, &kafsasyncd_async_attnq, link) { + op->call->app_attn_func = kafsasyncd_null_call_attn_func; + op->call->app_error_func = kafsasyncd_null_call_error_func; + remove_wait_queue(&op->call->waitq, &op->waiter); + } + + spin_unlock(&kafsasyncd_async_lock); + + /* abort all the operations */ + while (!list_empty(&kafsasyncd_async_attnq)) { + op = list_entry(kafsasyncd_async_attnq.next, struct afs_async_op, link); + list_del_init(&op->link); + + rxrpc_call_abort(op->call, -EIO); + rxrpc_put_call(op->call); + op->call = NULL; + + op->ops->discard(op); + } + + /* and that's all */ + _leave(""); + complete_and_exit(&kafsasyncd_dead, 0); + +} /* end kafsasyncd() */ + +/*****************************************************************************/ +/* + * begin an operation + * - place operation on busy queue + */ +void afs_kafsasyncd_begin_op(struct afs_async_op *op) +{ + _enter(""); + + spin_lock(&kafsasyncd_async_lock); + + init_waitqueue_entry(&op->waiter, kafsasyncd_task); + add_wait_queue(&op->call->waitq, &op->waiter); + + list_del(&op->link); + list_add_tail(&op->link, &kafsasyncd_async_busyq); + + spin_unlock(&kafsasyncd_async_lock); + + _leave(""); +} /* end afs_kafsasyncd_begin_op() */ + +/*****************************************************************************/ +/* + * request attention for an operation + * - move to attention queue + */ +void afs_kafsasyncd_attend_op(struct afs_async_op *op) +{ + _enter(""); + + spin_lock(&kafsasyncd_async_lock); + + list_del(&op->link); + list_add_tail(&op->link, &kafsasyncd_async_attnq); + + spin_unlock(&kafsasyncd_async_lock); + + wake_up(&kafsasyncd_sleepq); + + _leave(""); +} /* end afs_kafsasyncd_attend_op() */ + +/*****************************************************************************/ +/* + * terminate an operation + * - remove from either queue + */ +void afs_kafsasyncd_terminate_op(struct afs_async_op *op) +{ + _enter(""); + + spin_lock(&kafsasyncd_async_lock); + + if (!list_empty(&op->link)) { + list_del_init(&op->link); + remove_wait_queue(&op->call->waitq, &op->waiter); + } + + spin_unlock(&kafsasyncd_async_lock); + + wake_up(&kafsasyncd_sleepq); + + _leave(""); +} /* end afs_kafsasyncd_terminate_op() */ diff --git a/fs/afs/kafsasyncd.h b/fs/afs/kafsasyncd.h new file mode 100644 index 00000000000..791803f9a6f --- /dev/null +++ b/fs/afs/kafsasyncd.h @@ -0,0 +1,52 @@ +/* kafsasyncd.h: AFS asynchronous operation daemon + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_KAFSASYNCD_H +#define _LINUX_AFS_KAFSASYNCD_H + +#include "types.h" + +struct afs_async_op; + +struct afs_async_op_ops { + void (*attend)(struct afs_async_op *op); + void (*discard)(struct afs_async_op *op); +}; + +/*****************************************************************************/ +/* + * asynchronous operation record + */ +struct afs_async_op +{ + struct list_head link; + struct afs_server *server; /* server being contacted */ + struct rxrpc_call *call; /* RxRPC call performing op */ + wait_queue_t waiter; /* wait queue for kafsasyncd */ + const struct afs_async_op_ops *ops; /* operations */ +}; + +static inline void afs_async_op_init(struct afs_async_op *op, + const struct afs_async_op_ops *ops) +{ + INIT_LIST_HEAD(&op->link); + op->call = NULL; + op->ops = ops; +} + +extern int afs_kafsasyncd_start(void); +extern void afs_kafsasyncd_stop(void); + +extern void afs_kafsasyncd_begin_op(struct afs_async_op *op); +extern void afs_kafsasyncd_attend_op(struct afs_async_op *op); +extern void afs_kafsasyncd_terminate_op(struct afs_async_op *op); + +#endif /* _LINUX_AFS_KAFSASYNCD_H */ diff --git a/fs/afs/kafstimod.c b/fs/afs/kafstimod.c new file mode 100644 index 00000000000..86e710dd057 --- /dev/null +++ b/fs/afs/kafstimod.c @@ -0,0 +1,204 @@ +/* kafstimod.c: AFS timeout daemon + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include "cell.h" +#include "volume.h" +#include "kafstimod.h" +#include <asm/errno.h> +#include "internal.h" + +static DECLARE_COMPLETION(kafstimod_alive); +static DECLARE_COMPLETION(kafstimod_dead); +static DECLARE_WAIT_QUEUE_HEAD(kafstimod_sleepq); +static int kafstimod_die; + +static LIST_HEAD(kafstimod_list); +static DEFINE_SPINLOCK(kafstimod_lock); + +static int kafstimod(void *arg); + +/*****************************************************************************/ +/* + * start the timeout daemon + */ +int afs_kafstimod_start(void) +{ + int ret; + + ret = kernel_thread(kafstimod, NULL, 0); + if (ret < 0) + return ret; + + wait_for_completion(&kafstimod_alive); + + return ret; +} /* end afs_kafstimod_start() */ + +/*****************************************************************************/ +/* + * stop the timeout daemon + */ +void afs_kafstimod_stop(void) +{ + /* get rid of my daemon */ + kafstimod_die = 1; + wake_up(&kafstimod_sleepq); + wait_for_completion(&kafstimod_dead); + +} /* end afs_kafstimod_stop() */ + +/*****************************************************************************/ +/* + * timeout processing daemon + */ +static int kafstimod(void *arg) +{ + struct afs_timer *timer; + + DECLARE_WAITQUEUE(myself, current); + + printk("kAFS: Started kafstimod %d\n", current->pid); + + daemonize("kafstimod"); + + complete(&kafstimod_alive); + + /* loop around looking for things to attend to */ + loop: + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&kafstimod_sleepq, &myself); + + for (;;) { + unsigned long jif; + signed long timeout; + + /* deal with the server being asked to die */ + if (kafstimod_die) { + remove_wait_queue(&kafstimod_sleepq, &myself); + _leave(""); + complete_and_exit(&kafstimod_dead, 0); + } + + try_to_freeze(PF_FREEZE); + + /* discard pending signals */ + afs_discard_my_signals(); + + /* work out the time to elapse before the next event */ + spin_lock(&kafstimod_lock); + if (list_empty(&kafstimod_list)) { + timeout = MAX_SCHEDULE_TIMEOUT; + } + else { + timer = list_entry(kafstimod_list.next, + struct afs_timer, link); + timeout = timer->timo_jif; + jif = jiffies; + + if (time_before_eq((unsigned long) timeout, jif)) + goto immediate; + + else { + timeout = (long) timeout - (long) jiffies; + } + } + spin_unlock(&kafstimod_lock); + + schedule_timeout(timeout); + + set_current_state(TASK_INTERRUPTIBLE); + } + + /* the thing on the front of the queue needs processing + * - we come here with the lock held and timer pointing to the expired + * entry + */ + immediate: + remove_wait_queue(&kafstimod_sleepq, &myself); + set_current_state(TASK_RUNNING); + + _debug("@@@ Begin Timeout of %p", timer); + + /* dequeue the timer */ + list_del_init(&timer->link); + spin_unlock(&kafstimod_lock); + + /* call the timeout function */ + timer->ops->timed_out(timer); + + _debug("@@@ End Timeout"); + goto loop; + +} /* end kafstimod() */ + +/*****************************************************************************/ +/* + * (re-)queue a timer + */ +void afs_kafstimod_add_timer(struct afs_timer *timer, unsigned long timeout) +{ + struct afs_timer *ptimer; + struct list_head *_p; + + _enter("%p,%lu", timer, timeout); + + spin_lock(&kafstimod_lock); + + list_del(&timer->link); + + /* the timer was deferred or reset - put it back in the queue at the + * right place */ + timer->timo_jif = jiffies + timeout; + + list_for_each(_p, &kafstimod_list) { + ptimer = list_entry(_p, struct afs_timer, link); + if (time_before(timer->timo_jif, ptimer->timo_jif)) + break; + } + + list_add_tail(&timer->link, _p); /* insert before stopping point */ + + spin_unlock(&kafstimod_lock); + + wake_up(&kafstimod_sleepq); + + _leave(""); +} /* end afs_kafstimod_add_timer() */ + +/*****************************************************************************/ +/* + * dequeue a timer + * - returns 0 if the timer was deleted or -ENOENT if it wasn't queued + */ +int afs_kafstimod_del_timer(struct afs_timer *timer) +{ + int ret = 0; + + _enter("%p", timer); + + spin_lock(&kafstimod_lock); + + if (list_empty(&timer->link)) + ret = -ENOENT; + else + list_del_init(&timer->link); + + spin_unlock(&kafstimod_lock); + + wake_up(&kafstimod_sleepq); + + _leave(" = %d", ret); + return ret; +} /* end afs_kafstimod_del_timer() */ diff --git a/fs/afs/kafstimod.h b/fs/afs/kafstimod.h new file mode 100644 index 00000000000..e312f1a61a7 --- /dev/null +++ b/fs/afs/kafstimod.h @@ -0,0 +1,49 @@ +/* kafstimod.h: AFS timeout daemon + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_KAFSTIMOD_H +#define _LINUX_AFS_KAFSTIMOD_H + +#include "types.h" + +struct afs_timer; + +struct afs_timer_ops { + /* called when the front of the timer queue has timed out */ + void (*timed_out)(struct afs_timer *timer); +}; + +/*****************************************************************************/ +/* + * AFS timer/timeout record + */ +struct afs_timer +{ + struct list_head link; /* link in timer queue */ + unsigned long timo_jif; /* timeout time */ + const struct afs_timer_ops *ops; /* timeout expiry function */ +}; + +static inline void afs_timer_init(struct afs_timer *timer, + const struct afs_timer_ops *ops) +{ + INIT_LIST_HEAD(&timer->link); + timer->ops = ops; +} + +extern int afs_kafstimod_start(void); +extern void afs_kafstimod_stop(void); + +extern void afs_kafstimod_add_timer(struct afs_timer *timer, + unsigned long timeout); +extern int afs_kafstimod_del_timer(struct afs_timer *timer); + +#endif /* _LINUX_AFS_KAFSTIMOD_H */ diff --git a/fs/afs/main.c b/fs/afs/main.c new file mode 100644 index 00000000000..913c689bdb3 --- /dev/null +++ b/fs/afs/main.c @@ -0,0 +1,286 @@ +/* main.c: AFS client file system + * + * Copyright (C) 2002 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/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/completion.h> +#include <rxrpc/rxrpc.h> +#include <rxrpc/transport.h> +#include <rxrpc/call.h> +#include <rxrpc/peer.h> +#include "cache.h" +#include "cell.h" +#include "server.h" +#include "fsclient.h" +#include "cmservice.h" +#include "kafstimod.h" +#include "kafsasyncd.h" +#include "internal.h" + +struct rxrpc_transport *afs_transport; + +static int afs_adding_peer(struct rxrpc_peer *peer); +static void afs_discarding_peer(struct rxrpc_peer *peer); + + +MODULE_DESCRIPTION("AFS Client File System"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +static char *rootcell; + +module_param(rootcell, charp, 0); +MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list"); + + +static struct rxrpc_peer_ops afs_peer_ops = { + .adding = afs_adding_peer, + .discarding = afs_discarding_peer, +}; + +struct list_head afs_cb_hash_tbl[AFS_CB_HASH_COUNT]; +DEFINE_SPINLOCK(afs_cb_hash_lock); + +#ifdef AFS_CACHING_SUPPORT +static struct cachefs_netfs_operations afs_cache_ops = { + .get_page_cookie = afs_cache_get_page_cookie, +}; + +struct cachefs_netfs afs_cache_netfs = { + .name = "afs", + .version = 0, + .ops = &afs_cache_ops, +}; +#endif + +/*****************************************************************************/ +/* + * initialise the AFS client FS module + */ +static int __init afs_init(void) +{ + int loop, ret; + + printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 registering.\n"); + + /* initialise the callback hash table */ + spin_lock_init(&afs_cb_hash_lock); + for (loop = AFS_CB_HASH_COUNT - 1; loop >= 0; loop--) + INIT_LIST_HEAD(&afs_cb_hash_tbl[loop]); + + /* register the /proc stuff */ + ret = afs_proc_init(); + if (ret < 0) + return ret; + +#ifdef AFS_CACHING_SUPPORT + /* we want to be able to cache */ + ret = cachefs_register_netfs(&afs_cache_netfs, + &afs_cache_cell_index_def); + if (ret < 0) + goto error; +#endif + +#ifdef CONFIG_KEYS_TURNED_OFF + ret = afs_key_register(); + if (ret < 0) + goto error_cache; +#endif + + /* initialise the cell DB */ + ret = afs_cell_init(rootcell); + if (ret < 0) + goto error_keys; + + /* start the timeout daemon */ + ret = afs_kafstimod_start(); + if (ret < 0) + goto error_keys; + + /* start the async operation daemon */ + ret = afs_kafsasyncd_start(); + if (ret < 0) + goto error_kafstimod; + + /* create the RxRPC transport */ + ret = rxrpc_create_transport(7001, &afs_transport); + if (ret < 0) + goto error_kafsasyncd; + + afs_transport->peer_ops = &afs_peer_ops; + + /* register the filesystems */ + ret = afs_fs_init(); + if (ret < 0) + goto error_transport; + + return ret; + + error_transport: + rxrpc_put_transport(afs_transport); + error_kafsasyncd: + afs_kafsasyncd_stop(); + error_kafstimod: + afs_kafstimod_stop(); + error_keys: +#ifdef CONFIG_KEYS_TURNED_OFF + afs_key_unregister(); + error_cache: +#endif +#ifdef AFS_CACHING_SUPPORT + cachefs_unregister_netfs(&afs_cache_netfs); + error: +#endif + afs_cell_purge(); + afs_proc_cleanup(); + printk(KERN_ERR "kAFS: failed to register: %d\n", ret); + return ret; +} /* end afs_init() */ + +/* XXX late_initcall is kludgy, but the only alternative seems to create + * a transport upon the first mount, which is worse. Or is it? + */ +late_initcall(afs_init); /* must be called after net/ to create socket */ +/*****************************************************************************/ +/* + * clean up on module removal + */ +static void __exit afs_exit(void) +{ + printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 unregistering.\n"); + + afs_fs_exit(); + rxrpc_put_transport(afs_transport); + afs_kafstimod_stop(); + afs_kafsasyncd_stop(); + afs_cell_purge(); +#ifdef CONFIG_KEYS_TURNED_OFF + afs_key_unregister(); +#endif +#ifdef AFS_CACHING_SUPPORT + cachefs_unregister_netfs(&afs_cache_netfs); +#endif + afs_proc_cleanup(); + +} /* end afs_exit() */ + +module_exit(afs_exit); + +/*****************************************************************************/ +/* + * notification that new peer record is being added + * - called from krxsecd + * - return an error to induce an abort + * - mustn't sleep (caller holds an rwlock) + */ +static int afs_adding_peer(struct rxrpc_peer *peer) +{ + struct afs_server *server; + int ret; + + _debug("kAFS: Adding new peer %08x\n", ntohl(peer->addr.s_addr)); + + /* determine which server the peer resides in (if any) */ + ret = afs_server_find_by_peer(peer, &server); + if (ret < 0) + return ret; /* none that we recognise, so abort */ + + _debug("Server %p{u=%d}\n", server, atomic_read(&server->usage)); + + _debug("Cell %p{u=%d}\n", + server->cell, atomic_read(&server->cell->usage)); + + /* cross-point the structs under a global lock */ + spin_lock(&afs_server_peer_lock); + peer->user = server; + server->peer = peer; + spin_unlock(&afs_server_peer_lock); + + afs_put_server(server); + + return 0; +} /* end afs_adding_peer() */ + +/*****************************************************************************/ +/* + * notification that a peer record is being discarded + * - called from krxiod or krxsecd + */ +static void afs_discarding_peer(struct rxrpc_peer *peer) +{ + struct afs_server *server; + + _enter("%p",peer); + + _debug("Discarding peer %08x (rtt=%lu.%lumS)\n", + ntohl(peer->addr.s_addr), + (long) (peer->rtt / 1000), + (long) (peer->rtt % 1000)); + + /* uncross-point the structs under a global lock */ + spin_lock(&afs_server_peer_lock); + server = peer->user; + if (server) { + peer->user = NULL; + server->peer = NULL; + } + spin_unlock(&afs_server_peer_lock); + + _leave(""); + +} /* end afs_discarding_peer() */ + +/*****************************************************************************/ +/* + * clear the dead space between task_struct and kernel stack + * - called by supplying -finstrument-functions to gcc + */ +#if 0 +void __cyg_profile_func_enter (void *this_fn, void *call_site) +__attribute__((no_instrument_function)); + +void __cyg_profile_func_enter (void *this_fn, void *call_site) +{ + asm volatile(" movl %%esp,%%edi \n" + " andl %0,%%edi \n" + " addl %1,%%edi \n" + " movl %%esp,%%ecx \n" + " subl %%edi,%%ecx \n" + " shrl $2,%%ecx \n" + " movl $0xedededed,%%eax \n" + " rep stosl \n" + : + : "i"(~(THREAD_SIZE - 1)), "i"(sizeof(struct thread_info)) + : "eax", "ecx", "edi", "memory", "cc" + ); +} + +void __cyg_profile_func_exit(void *this_fn, void *call_site) +__attribute__((no_instrument_function)); + +void __cyg_profile_func_exit(void *this_fn, void *call_site) +{ + asm volatile(" movl %%esp,%%edi \n" + " andl %0,%%edi \n" + " addl %1,%%edi \n" + " movl %%esp,%%ecx \n" + " subl %%edi,%%ecx \n" + " shrl $2,%%ecx \n" + " movl $0xdadadada,%%eax \n" + " rep stosl \n" + : + : "i"(~(THREAD_SIZE - 1)), "i"(sizeof(struct thread_info)) + : "eax", "ecx", "edi", "memory", "cc" + ); +} +#endif diff --git a/fs/afs/misc.c b/fs/afs/misc.c new file mode 100644 index 00000000000..e4fce66d76e --- /dev/null +++ b/fs/afs/misc.c @@ -0,0 +1,39 @@ +/* misc.c: miscellaneous bits + * + * Copyright (C) 2002 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/module.h> +#include <linux/errno.h> +#include "errors.h" +#include "internal.h" + +/*****************************************************************************/ +/* + * convert an AFS abort code to a Linux error number + */ +int afs_abort_to_error(int abortcode) +{ + switch (abortcode) { + case VSALVAGE: return -EIO; + case VNOVNODE: return -ENOENT; + case VNOVOL: return -ENXIO; + case VVOLEXISTS: return -EEXIST; + case VNOSERVICE: return -EIO; + case VOFFLINE: return -ENOENT; + case VONLINE: return -EEXIST; + case VDISKFULL: return -ENOSPC; + case VOVERQUOTA: return -EDQUOT; + case VBUSY: return -EBUSY; + case VMOVED: return -ENXIO; + default: return -EIO; + } + +} /* end afs_abort_to_error() */ diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c new file mode 100644 index 00000000000..bfc28abe1cb --- /dev/null +++ b/fs/afs/mntpt.c @@ -0,0 +1,287 @@ +/* mntpt.c: mountpoint management + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <linux/namespace.h> +#include "super.h" +#include "cell.h" +#include "volume.h" +#include "vnode.h" +#include "internal.h" + + +static struct dentry *afs_mntpt_lookup(struct inode *dir, + struct dentry *dentry, + struct nameidata *nd); +static int afs_mntpt_open(struct inode *inode, struct file *file); +static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd); + +struct file_operations afs_mntpt_file_operations = { + .open = afs_mntpt_open, +}; + +struct inode_operations afs_mntpt_inode_operations = { + .lookup = afs_mntpt_lookup, + .follow_link = afs_mntpt_follow_link, + .readlink = page_readlink, + .getattr = afs_inode_getattr, +}; + +static LIST_HEAD(afs_vfsmounts); + +static void afs_mntpt_expiry_timed_out(struct afs_timer *timer); + +struct afs_timer_ops afs_mntpt_expiry_timer_ops = { + .timed_out = afs_mntpt_expiry_timed_out, +}; + +struct afs_timer afs_mntpt_expiry_timer; + +unsigned long afs_mntpt_expiry_timeout = 20; + +/*****************************************************************************/ +/* + * check a symbolic link to see whether it actually encodes a mountpoint + * - sets the AFS_VNODE_MOUNTPOINT flag on the vnode appropriately + */ +int afs_mntpt_check_symlink(struct afs_vnode *vnode) +{ + struct page *page; + filler_t *filler; + size_t size; + char *buf; + int ret; + + _enter("{%u,%u}", vnode->fid.vnode, vnode->fid.unique); + + /* read the contents of the symlink into the pagecache */ + filler = (filler_t *) AFS_VNODE_TO_I(vnode)->i_mapping->a_ops->readpage; + + page = read_cache_page(AFS_VNODE_TO_I(vnode)->i_mapping, 0, + filler, NULL); + if (IS_ERR(page)) { + ret = PTR_ERR(page); + goto out; + } + + ret = -EIO; + wait_on_page_locked(page); + buf = kmap(page); + if (!PageUptodate(page)) + goto out_free; + if (PageError(page)) + goto out_free; + + /* examine the symlink's contents */ + size = vnode->status.size; + _debug("symlink to %*.*s", size, (int) size, buf); + + if (size > 2 && + (buf[0] == '%' || buf[0] == '#') && + buf[size - 1] == '.' + ) { + _debug("symlink is a mountpoint"); + spin_lock(&vnode->lock); + vnode->flags |= AFS_VNODE_MOUNTPOINT; + spin_unlock(&vnode->lock); + } + + ret = 0; + + out_free: + kunmap(page); + page_cache_release(page); + out: + _leave(" = %d", ret); + return ret; + +} /* end afs_mntpt_check_symlink() */ + +/*****************************************************************************/ +/* + * no valid lookup procedure on this sort of dir + */ +static struct dentry *afs_mntpt_lookup(struct inode *dir, + struct dentry *dentry, + struct nameidata *nd) +{ + kenter("%p,%p{%p{%s},%s}", + dir, + dentry, + dentry->d_parent, + dentry->d_parent ? + dentry->d_parent->d_name.name : (const unsigned char *) "", + dentry->d_name.name); + + return ERR_PTR(-EREMOTE); +} /* end afs_mntpt_lookup() */ + +/*****************************************************************************/ +/* + * no valid open procedure on this sort of dir + */ +static int afs_mntpt_open(struct inode *inode, struct file *file) +{ + kenter("%p,%p{%p{%s},%s}", + inode, file, + file->f_dentry->d_parent, + file->f_dentry->d_parent ? + file->f_dentry->d_parent->d_name.name : + (const unsigned char *) "", + file->f_dentry->d_name.name); + + return -EREMOTE; +} /* end afs_mntpt_open() */ + +/*****************************************************************************/ +/* + * create a vfsmount to be automounted + */ +static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt) +{ + struct afs_super_info *super; + struct vfsmount *mnt; + struct page *page = NULL; + size_t size; + char *buf, *devname = NULL, *options = NULL; + filler_t *filler; + int ret; + + kenter("{%s}", mntpt->d_name.name); + + BUG_ON(!mntpt->d_inode); + + ret = -EINVAL; + size = mntpt->d_inode->i_size; + if (size > PAGE_SIZE - 1) + goto error; + + ret = -ENOMEM; + devname = (char *) get_zeroed_page(GFP_KERNEL); + if (!devname) + goto error; + + options = (char *) get_zeroed_page(GFP_KERNEL); + if (!options) + goto error; + + /* read the contents of the AFS special symlink */ + filler = (filler_t *)mntpt->d_inode->i_mapping->a_ops->readpage; + + page = read_cache_page(mntpt->d_inode->i_mapping, 0, filler, NULL); + if (IS_ERR(page)) { + ret = PTR_ERR(page); + goto error; + } + + ret = -EIO; + wait_on_page_locked(page); + if (!PageUptodate(page) || PageError(page)) + goto error; + + buf = kmap(page); + memcpy(devname, buf, size); + kunmap(page); + page_cache_release(page); + page = NULL; + + /* work out what options we want */ + super = AFS_FS_S(mntpt->d_sb); + memcpy(options, "cell=", 5); + strcpy(options + 5, super->volume->cell->name); + if (super->volume->type == AFSVL_RWVOL) + strcat(options, ",rwpath"); + + /* try and do the mount */ + kdebug("--- attempting mount %s -o %s ---", devname, options); + mnt = do_kern_mount("afs", 0, devname, options); + kdebug("--- mount result %p ---", mnt); + + free_page((unsigned long) devname); + free_page((unsigned long) options); + kleave(" = %p", mnt); + return mnt; + + error: + if (page) + page_cache_release(page); + if (devname) + free_page((unsigned long) devname); + if (options) + free_page((unsigned long) options); + kleave(" = %d", ret); + return ERR_PTR(ret); +} /* end afs_mntpt_do_automount() */ + +/*****************************************************************************/ +/* + * follow a link from a mountpoint directory, thus causing it to be mounted + */ +static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + struct vfsmount *newmnt; + struct dentry *old_dentry; + int err; + + kenter("%p{%s},{%s:%p{%s}}", + dentry, + dentry->d_name.name, + nd->mnt->mnt_devname, + dentry, + nd->dentry->d_name.name); + + newmnt = afs_mntpt_do_automount(dentry); + if (IS_ERR(newmnt)) { + path_release(nd); + return PTR_ERR(newmnt); + } + + old_dentry = nd->dentry; + nd->dentry = dentry; + err = do_add_mount(newmnt, nd, 0, &afs_vfsmounts); + nd->dentry = old_dentry; + + path_release(nd); + + if (!err) { + mntget(newmnt); + nd->mnt = newmnt; + dget(newmnt->mnt_root); + nd->dentry = newmnt->mnt_root; + } + + kleave(" = %d", err); + return err; +} /* end afs_mntpt_follow_link() */ + +/*****************************************************************************/ +/* + * handle mountpoint expiry timer going off + */ +static void afs_mntpt_expiry_timed_out(struct afs_timer *timer) +{ + kenter(""); + + mark_mounts_for_expiry(&afs_vfsmounts); + + afs_kafstimod_add_timer(&afs_mntpt_expiry_timer, + afs_mntpt_expiry_timeout * HZ); + + kleave(""); +} /* end afs_mntpt_expiry_timed_out() */ diff --git a/fs/afs/mount.h b/fs/afs/mount.h new file mode 100644 index 00000000000..9d2f46ec549 --- /dev/null +++ b/fs/afs/mount.h @@ -0,0 +1,23 @@ +/* mount.h: mount parameters + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_MOUNT_H +#define _LINUX_AFS_MOUNT_H + +struct afs_mountdata { + const char *volume; /* name of volume */ + const char *cell; /* name of cell containing volume */ + const char *cache; /* name of cache block device */ + size_t nservers; /* number of server addresses listed */ + uint32_t servers[10]; /* IP addresses of servers in this cell */ +}; + +#endif /* _LINUX_AFS_MOUNT_H */ diff --git a/fs/afs/proc.c b/fs/afs/proc.c new file mode 100644 index 00000000000..9c81b8f7eef --- /dev/null +++ b/fs/afs/proc.c @@ -0,0 +1,857 @@ +/* proc.c: /proc interface for AFS + * + * Copyright (C) 2002 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/sched.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include "cell.h" +#include "volume.h" +#include <asm/uaccess.h> +#include "internal.h" + +static struct proc_dir_entry *proc_afs; + + +static int afs_proc_cells_open(struct inode *inode, struct file *file); +static void *afs_proc_cells_start(struct seq_file *p, loff_t *pos); +static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos); +static void afs_proc_cells_stop(struct seq_file *p, void *v); +static int afs_proc_cells_show(struct seq_file *m, void *v); +static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf, + size_t size, loff_t *_pos); + +static 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, +}; + +static struct file_operations afs_proc_cells_fops = { + .open = afs_proc_cells_open, + .read = seq_read, + .write = afs_proc_cells_write, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int afs_proc_rootcell_open(struct inode *inode, struct file *file); +static int afs_proc_rootcell_release(struct inode *inode, struct file *file); +static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf, + size_t size, loff_t *_pos); +static ssize_t afs_proc_rootcell_write(struct file *file, + const char __user *buf, + size_t size, loff_t *_pos); + +static struct file_operations afs_proc_rootcell_fops = { + .open = afs_proc_rootcell_open, + .read = afs_proc_rootcell_read, + .write = afs_proc_rootcell_write, + .llseek = no_llseek, + .release = afs_proc_rootcell_release +}; + +static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file); +static int afs_proc_cell_volumes_release(struct inode *inode, + struct file *file); +static void *afs_proc_cell_volumes_start(struct seq_file *p, loff_t *pos); +static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v, + loff_t *pos); +static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v); +static int afs_proc_cell_volumes_show(struct seq_file *m, void *v); + +static 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 struct file_operations afs_proc_cell_volumes_fops = { + .open = afs_proc_cell_volumes_open, + .read = seq_read, + .llseek = seq_lseek, + .release = afs_proc_cell_volumes_release, +}; + +static int afs_proc_cell_vlservers_open(struct inode *inode, + struct file *file); +static int afs_proc_cell_vlservers_release(struct inode *inode, + struct file *file); +static void *afs_proc_cell_vlservers_start(struct seq_file *p, loff_t *pos); +static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v, + loff_t *pos); +static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v); +static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v); + +static 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, +}; + +static struct file_operations afs_proc_cell_vlservers_fops = { + .open = afs_proc_cell_vlservers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = afs_proc_cell_vlservers_release, +}; + +static int afs_proc_cell_servers_open(struct inode *inode, struct file *file); +static int afs_proc_cell_servers_release(struct inode *inode, + struct file *file); +static void *afs_proc_cell_servers_start(struct seq_file *p, loff_t *pos); +static void *afs_proc_cell_servers_next(struct seq_file *p, void *v, + loff_t *pos); +static void afs_proc_cell_servers_stop(struct seq_file *p, void *v); +static int afs_proc_cell_servers_show(struct seq_file *m, void *v); + +static struct seq_operations afs_proc_cell_servers_ops = { + .start = afs_proc_cell_servers_start, + .next = afs_proc_cell_servers_next, + .stop = afs_proc_cell_servers_stop, + .show = afs_proc_cell_servers_show, +}; + +static struct file_operations afs_proc_cell_servers_fops = { + .open = afs_proc_cell_servers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = afs_proc_cell_servers_release, +}; + +/*****************************************************************************/ +/* + * initialise the /proc/fs/afs/ directory + */ +int afs_proc_init(void) +{ + struct proc_dir_entry *p; + + _enter(""); + + proc_afs = proc_mkdir("fs/afs", NULL); + if (!proc_afs) + goto error; + proc_afs->owner = THIS_MODULE; + + p = create_proc_entry("cells", 0, proc_afs); + if (!p) + goto error_proc; + p->proc_fops = &afs_proc_cells_fops; + p->owner = THIS_MODULE; + + p = create_proc_entry("rootcell", 0, proc_afs); + if (!p) + goto error_cells; + p->proc_fops = &afs_proc_rootcell_fops; + p->owner = THIS_MODULE; + + _leave(" = 0"); + return 0; + + error_cells: + remove_proc_entry("cells", proc_afs); + error_proc: + remove_proc_entry("fs/afs", NULL); + error: + _leave(" = -ENOMEM"); + return -ENOMEM; + +} /* end afs_proc_init() */ + +/*****************************************************************************/ +/* + * clean up the /proc/fs/afs/ directory + */ +void afs_proc_cleanup(void) +{ + remove_proc_entry("cells", proc_afs); + + remove_proc_entry("fs/afs", NULL); + +} /* end afs_proc_cleanup() */ + +/*****************************************************************************/ +/* + * open "/proc/fs/afs/cells" which provides a summary of extant cells + */ +static int afs_proc_cells_open(struct inode *inode, struct file *file) +{ + struct seq_file *m; + int ret; + + ret = seq_open(file, &afs_proc_cells_ops); + if (ret < 0) + return ret; + + m = file->private_data; + m->private = PDE(inode)->data; + + return 0; +} /* end afs_proc_cells_open() */ + +/*****************************************************************************/ +/* + * set up the iterator to start reading from the cells list and return the + * first item + */ +static void *afs_proc_cells_start(struct seq_file *m, loff_t *_pos) +{ + struct list_head *_p; + loff_t pos = *_pos; + + /* lock the list against modification */ + down_read(&afs_proc_cells_sem); + + /* allow for the header line */ + if (!pos) + return (void *) 1; + pos--; + + /* find the n'th element in the list */ + list_for_each(_p, &afs_proc_cells) + if (!pos--) + break; + + return _p != &afs_proc_cells ? _p : NULL; +} /* end afs_proc_cells_start() */ + +/*****************************************************************************/ +/* + * move to next cell in cells list + */ +static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos) +{ + struct list_head *_p; + + (*pos)++; + + _p = v; + _p = v == (void *) 1 ? afs_proc_cells.next : _p->next; + + return _p != &afs_proc_cells ? _p : NULL; +} /* end afs_proc_cells_next() */ + +/*****************************************************************************/ +/* + * clean up after reading from the cells list + */ +static void afs_proc_cells_stop(struct seq_file *p, void *v) +{ + up_read(&afs_proc_cells_sem); + +} /* end afs_proc_cells_stop() */ + +/*****************************************************************************/ +/* + * display a header line followed by a load of cell lines + */ +static int afs_proc_cells_show(struct seq_file *m, void *v) +{ + struct afs_cell *cell = list_entry(v, struct afs_cell, proc_link); + + /* display header on line 1 */ + if (v == (void *) 1) { + seq_puts(m, "USE NAME\n"); + return 0; + } + + /* display one cell per line on subsequent lines */ + seq_printf(m, "%3d %s\n", atomic_read(&cell->usage), cell->name); + + return 0; +} /* end afs_proc_cells_show() */ + +/*****************************************************************************/ +/* + * handle writes to /proc/fs/afs/cells + * - to add cells: echo "add <cellname> <IP>[:<IP>][:<IP>]" + */ +static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf, + size_t size, loff_t *_pos) +{ + char *kbuf, *name, *args; + int ret; + + /* start by dragging the command into memory */ + if (size <= 1 || size >= PAGE_SIZE) + return -EINVAL; + + kbuf = kmalloc(size + 1, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = -EFAULT; + if (copy_from_user(kbuf, buf, size) != 0) + goto done; + kbuf[size] = 0; + + /* trim to first NL */ + name = memchr(kbuf, '\n', size); + if (name) + *name = 0; + + /* split into command, name and argslist */ + name = strchr(kbuf, ' '); + if (!name) + goto inval; + do { + *name++ = 0; + } while(*name == ' '); + if (!*name) + goto inval; + + args = strchr(name, ' '); + if (!args) + goto inval; + do { + *args++ = 0; + } while(*args == ' '); + if (!*args) + goto inval; + + /* determine command to perform */ + _debug("cmd=%s name=%s args=%s", kbuf, name, args); + + if (strcmp(kbuf, "add") == 0) { + struct afs_cell *cell; + ret = afs_cell_create(name, args, &cell); + if (ret < 0) + goto done; + + printk("kAFS: Added new cell '%s'\n", name); + } + else { + goto inval; + } + + ret = size; + + done: + kfree(kbuf); + _leave(" = %d", ret); + return ret; + + inval: + ret = -EINVAL; + printk("kAFS: Invalid Command on /proc/fs/afs/cells file\n"); + goto done; +} /* end afs_proc_cells_write() */ + +/*****************************************************************************/ +/* + * Stubs for /proc/fs/afs/rootcell + */ +static int afs_proc_rootcell_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int afs_proc_rootcell_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf, + size_t size, loff_t *_pos) +{ + return 0; +} + +/*****************************************************************************/ +/* + * handle writes to /proc/fs/afs/rootcell + * - to initialize rootcell: echo "cell.name:192.168.231.14" + */ +static ssize_t afs_proc_rootcell_write(struct file *file, + const char __user *buf, + size_t size, loff_t *_pos) +{ + char *kbuf, *s; + int ret; + + /* start by dragging the command into memory */ + if (size <= 1 || size >= PAGE_SIZE) + return -EINVAL; + + ret = -ENOMEM; + kbuf = kmalloc(size + 1, GFP_KERNEL); + if (!kbuf) + goto nomem; + + ret = -EFAULT; + if (copy_from_user(kbuf, buf, size) != 0) + goto infault; + kbuf[size] = 0; + + /* trim to first NL */ + s = memchr(kbuf, '\n', size); + if (s) + *s = 0; + + /* determine command to perform */ + _debug("rootcell=%s", kbuf); + + ret = afs_cell_init(kbuf); + if (ret >= 0) + ret = size; /* consume everything, always */ + + infault: + kfree(kbuf); + nomem: + _leave(" = %d", ret); + return ret; +} /* end afs_proc_rootcell_write() */ + +/*****************************************************************************/ +/* + * initialise /proc/fs/afs/<cell>/ + */ +int afs_proc_cell_setup(struct afs_cell *cell) +{ + struct proc_dir_entry *p; + + _enter("%p{%s}", cell, cell->name); + + cell->proc_dir = proc_mkdir(cell->name, proc_afs); + if (!cell->proc_dir) + return -ENOMEM; + + p = create_proc_entry("servers", 0, cell->proc_dir); + if (!p) + goto error_proc; + p->proc_fops = &afs_proc_cell_servers_fops; + p->owner = THIS_MODULE; + p->data = cell; + + p = create_proc_entry("vlservers", 0, cell->proc_dir); + if (!p) + goto error_servers; + p->proc_fops = &afs_proc_cell_vlservers_fops; + p->owner = THIS_MODULE; + p->data = cell; + + p = create_proc_entry("volumes", 0, cell->proc_dir); + if (!p) + goto error_vlservers; + p->proc_fops = &afs_proc_cell_volumes_fops; + p->owner = THIS_MODULE; + p->data = cell; + + _leave(" = 0"); + return 0; + + error_vlservers: + remove_proc_entry("vlservers", cell->proc_dir); + error_servers: + remove_proc_entry("servers", cell->proc_dir); + error_proc: + remove_proc_entry(cell->name, proc_afs); + _leave(" = -ENOMEM"); + return -ENOMEM; +} /* end afs_proc_cell_setup() */ + +/*****************************************************************************/ +/* + * remove /proc/fs/afs/<cell>/ + */ +void afs_proc_cell_remove(struct afs_cell *cell) +{ + _enter(""); + + remove_proc_entry("volumes", cell->proc_dir); + remove_proc_entry("vlservers", cell->proc_dir); + remove_proc_entry("servers", cell->proc_dir); + remove_proc_entry(cell->name, proc_afs); + + _leave(""); +} /* end afs_proc_cell_remove() */ + +/*****************************************************************************/ +/* + * open "/proc/fs/afs/<cell>/volumes" which provides a summary of extant cells + */ +static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file) +{ + struct afs_cell *cell; + struct seq_file *m; + int ret; + + cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data); + if (!cell) + return -ENOENT; + + ret = seq_open(file, &afs_proc_cell_volumes_ops); + if (ret < 0) + return ret; + + m = file->private_data; + m->private = cell; + + return 0; +} /* end afs_proc_cell_volumes_open() */ + +/*****************************************************************************/ +/* + * close the file and release the ref to the cell + */ +static int afs_proc_cell_volumes_release(struct inode *inode, struct file *file) +{ + struct afs_cell *cell = PDE(inode)->data; + int ret; + + ret = seq_release(inode,file); + + afs_put_cell(cell); + + return ret; +} /* end afs_proc_cell_volumes_release() */ + +/*****************************************************************************/ +/* + * set up the iterator to start reading from the cells list and return the + * first item + */ +static void *afs_proc_cell_volumes_start(struct seq_file *m, loff_t *_pos) +{ + struct list_head *_p; + struct afs_cell *cell = m->private; + loff_t pos = *_pos; + + _enter("cell=%p pos=%Ld", cell, *_pos); + + /* lock the list against modification */ + down_read(&cell->vl_sem); + + /* allow for the header line */ + if (!pos) + return (void *) 1; + pos--; + + /* find the n'th element in the list */ + list_for_each(_p, &cell->vl_list) + if (!pos--) + break; + + return _p != &cell->vl_list ? _p : NULL; +} /* end afs_proc_cell_volumes_start() */ + +/*****************************************************************************/ +/* + * move to next cell in cells list + */ +static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v, + loff_t *_pos) +{ + struct list_head *_p; + struct afs_cell *cell = p->private; + + _enter("cell=%p pos=%Ld", cell, *_pos); + + (*_pos)++; + + _p = v; + _p = v == (void *) 1 ? cell->vl_list.next : _p->next; + + return _p != &cell->vl_list ? _p : NULL; +} /* end afs_proc_cell_volumes_next() */ + +/*****************************************************************************/ +/* + * clean up after reading from the cells list + */ +static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v) +{ + struct afs_cell *cell = p->private; + + up_read(&cell->vl_sem); + +} /* end afs_proc_cell_volumes_stop() */ + +/*****************************************************************************/ +/* + * display a header line followed by a load of volume lines + */ +static int afs_proc_cell_volumes_show(struct seq_file *m, void *v) +{ + struct afs_vlocation *vlocation = + list_entry(v, struct afs_vlocation, link); + + /* display header on line 1 */ + if (v == (void *) 1) { + seq_puts(m, "USE VLID[0] VLID[1] VLID[2] NAME\n"); + return 0; + } + + /* display one cell per line on subsequent lines */ + seq_printf(m, "%3d %08x %08x %08x %s\n", + atomic_read(&vlocation->usage), + vlocation->vldb.vid[0], + vlocation->vldb.vid[1], + vlocation->vldb.vid[2], + vlocation->vldb.name + ); + + return 0; +} /* end afs_proc_cell_volumes_show() */ + +/*****************************************************************************/ +/* + * open "/proc/fs/afs/<cell>/vlservers" which provides a list of volume + * location server + */ +static int afs_proc_cell_vlservers_open(struct inode *inode, struct file *file) +{ + struct afs_cell *cell; + struct seq_file *m; + int ret; + + cell = afs_get_cell_maybe((struct afs_cell**)&PDE(inode)->data); + if (!cell) + return -ENOENT; + + ret = seq_open(file,&afs_proc_cell_vlservers_ops); + if (ret<0) + return ret; + + m = file->private_data; + m->private = cell; + + return 0; +} /* end afs_proc_cell_vlservers_open() */ + +/*****************************************************************************/ +/* + * close the file and release the ref to the cell + */ +static int afs_proc_cell_vlservers_release(struct inode *inode, + struct file *file) +{ + struct afs_cell *cell = PDE(inode)->data; + int ret; + + ret = seq_release(inode,file); + + afs_put_cell(cell); + + return ret; +} /* end afs_proc_cell_vlservers_release() */ + +/*****************************************************************************/ +/* + * set up the iterator to start reading from the cells list and return the + * first item + */ +static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos) +{ + struct afs_cell *cell = m->private; + loff_t pos = *_pos; + + _enter("cell=%p pos=%Ld", cell, *_pos); + + /* lock the list against modification */ + down_read(&cell->vl_sem); + + /* allow for the header line */ + if (!pos) + return (void *) 1; + pos--; + + if (pos >= cell->vl_naddrs) + return NULL; + + return &cell->vl_addrs[pos]; +} /* end afs_proc_cell_vlservers_start() */ + +/*****************************************************************************/ +/* + * move to next cell in cells list + */ +static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v, + loff_t *_pos) +{ + struct afs_cell *cell = p->private; + loff_t pos; + + _enter("cell=%p{nad=%u} pos=%Ld", cell, cell->vl_naddrs, *_pos); + + pos = *_pos; + (*_pos)++; + if (pos >= cell->vl_naddrs) + return NULL; + + return &cell->vl_addrs[pos]; +} /* end afs_proc_cell_vlservers_next() */ + +/*****************************************************************************/ +/* + * clean up after reading from the cells list + */ +static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v) +{ + struct afs_cell *cell = p->private; + + up_read(&cell->vl_sem); + +} /* end afs_proc_cell_vlservers_stop() */ + +/*****************************************************************************/ +/* + * display a header line followed by a load of volume lines + */ +static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v) +{ + struct in_addr *addr = v; + + /* display header on line 1 */ + if (v == (struct in_addr *) 1) { + seq_puts(m, "ADDRESS\n"); + return 0; + } + + /* display one cell per line on subsequent lines */ + seq_printf(m, "%u.%u.%u.%u\n", NIPQUAD(addr->s_addr)); + + return 0; +} /* end afs_proc_cell_vlservers_show() */ + +/*****************************************************************************/ +/* + * open "/proc/fs/afs/<cell>/servers" which provides a summary of active + * servers + */ +static int afs_proc_cell_servers_open(struct inode *inode, struct file *file) +{ + struct afs_cell *cell; + struct seq_file *m; + int ret; + + cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data); + if (!cell) + return -ENOENT; + + ret = seq_open(file, &afs_proc_cell_servers_ops); + if (ret < 0) + return ret; + + m = file->private_data; + m->private = cell; + + return 0; +} /* end afs_proc_cell_servers_open() */ + +/*****************************************************************************/ +/* + * close the file and release the ref to the cell + */ +static int afs_proc_cell_servers_release(struct inode *inode, + struct file *file) +{ + struct afs_cell *cell = PDE(inode)->data; + int ret; + + ret = seq_release(inode, file); + + afs_put_cell(cell); + + return ret; +} /* end afs_proc_cell_servers_release() */ + +/*****************************************************************************/ +/* + * set up the iterator to start reading from the cells list and return the + * first item + */ +static void *afs_proc_cell_servers_start(struct seq_file *m, loff_t *_pos) +{ + struct list_head *_p; + struct afs_cell *cell = m->private; + loff_t pos = *_pos; + + _enter("cell=%p pos=%Ld", cell, *_pos); + + /* lock the list against modification */ + read_lock(&cell->sv_lock); + + /* allow for the header line */ + if (!pos) + return (void *) 1; + pos--; + + /* find the n'th element in the list */ + list_for_each(_p, &cell->sv_list) + if (!pos--) + break; + + return _p != &cell->sv_list ? _p : NULL; +} /* end afs_proc_cell_servers_start() */ + +/*****************************************************************************/ +/* + * move to next cell in cells list + */ +static void *afs_proc_cell_servers_next(struct seq_file *p, void *v, + loff_t *_pos) +{ + struct list_head *_p; + struct afs_cell *cell = p->private; + + _enter("cell=%p pos=%Ld", cell, *_pos); + + (*_pos)++; + + _p = v; + _p = v == (void *) 1 ? cell->sv_list.next : _p->next; + + return _p != &cell->sv_list ? _p : NULL; +} /* end afs_proc_cell_servers_next() */ + +/*****************************************************************************/ +/* + * clean up after reading from the cells list + */ +static void afs_proc_cell_servers_stop(struct seq_file *p, void *v) +{ + struct afs_cell *cell = p->private; + + read_unlock(&cell->sv_lock); + +} /* end afs_proc_cell_servers_stop() */ + +/*****************************************************************************/ +/* + * display a header line followed by a load of volume lines + */ +static int afs_proc_cell_servers_show(struct seq_file *m, void *v) +{ + struct afs_server *server = list_entry(v, struct afs_server, link); + char ipaddr[20]; + + /* display header on line 1 */ + if (v == (void *) 1) { + seq_puts(m, "USE ADDR STATE\n"); + return 0; + } + + /* display one cell per line on subsequent lines */ + sprintf(ipaddr, "%u.%u.%u.%u", NIPQUAD(server->addr)); + seq_printf(m, "%3d %-15.15s %5d\n", + atomic_read(&server->usage), + ipaddr, + server->fs_state + ); + + return 0; +} /* end afs_proc_cell_servers_show() */ diff --git a/fs/afs/server.c b/fs/afs/server.c new file mode 100644 index 00000000000..62b093aa41c --- /dev/null +++ b/fs/afs/server.c @@ -0,0 +1,502 @@ +/* server.c: AFS server record management + * + * Copyright (C) 2002 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/sched.h> +#include <linux/slab.h> +#include <rxrpc/peer.h> +#include <rxrpc/connection.h> +#include "volume.h" +#include "cell.h" +#include "server.h" +#include "transport.h" +#include "vlclient.h" +#include "kafstimod.h" +#include "internal.h" + +DEFINE_SPINLOCK(afs_server_peer_lock); + +#define FS_SERVICE_ID 1 /* AFS Volume Location Service ID */ +#define VL_SERVICE_ID 52 /* AFS Volume Location Service ID */ + +static void __afs_server_timeout(struct afs_timer *timer) +{ + struct afs_server *server = + list_entry(timer, struct afs_server, timeout); + + _debug("SERVER TIMEOUT [%p{u=%d}]", + server, atomic_read(&server->usage)); + + afs_server_do_timeout(server); +} + +static const struct afs_timer_ops afs_server_timer_ops = { + .timed_out = __afs_server_timeout, +}; + +/*****************************************************************************/ +/* + * lookup a server record in a cell + * - TODO: search the cell's server list + */ +int afs_server_lookup(struct afs_cell *cell, const struct in_addr *addr, + struct afs_server **_server) +{ + struct afs_server *server, *active, *zombie; + int loop; + + _enter("%p,%08x,", cell, ntohl(addr->s_addr)); + + /* allocate and initialise a server record */ + server = kmalloc(sizeof(struct afs_server), GFP_KERNEL); + if (!server) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + memset(server, 0, sizeof(struct afs_server)); + atomic_set(&server->usage, 1); + + INIT_LIST_HEAD(&server->link); + init_rwsem(&server->sem); + INIT_LIST_HEAD(&server->fs_callq); + spin_lock_init(&server->fs_lock); + INIT_LIST_HEAD(&server->cb_promises); + spin_lock_init(&server->cb_lock); + + for (loop = 0; loop < AFS_SERVER_CONN_LIST_SIZE; loop++) + server->fs_conn_cnt[loop] = 4; + + memcpy(&server->addr, addr, sizeof(struct in_addr)); + server->addr.s_addr = addr->s_addr; + + afs_timer_init(&server->timeout, &afs_server_timer_ops); + + /* add to the cell */ + write_lock(&cell->sv_lock); + + /* check the active list */ + list_for_each_entry(active, &cell->sv_list, link) { + if (active->addr.s_addr == addr->s_addr) + goto use_active_server; + } + + /* check the inactive list */ + spin_lock(&cell->sv_gylock); + list_for_each_entry(zombie, &cell->sv_graveyard, link) { + if (zombie->addr.s_addr == addr->s_addr) + goto resurrect_server; + } + spin_unlock(&cell->sv_gylock); + + afs_get_cell(cell); + server->cell = cell; + list_add_tail(&server->link, &cell->sv_list); + + write_unlock(&cell->sv_lock); + + *_server = server; + _leave(" = 0 (%p)", server); + return 0; + + /* found a matching active server */ + use_active_server: + _debug("active server"); + afs_get_server(active); + write_unlock(&cell->sv_lock); + + kfree(server); + + *_server = active; + _leave(" = 0 (%p)", active); + return 0; + + /* found a matching server in the graveyard, so resurrect it and + * dispose of the new record */ + resurrect_server: + _debug("resurrecting server"); + + list_del(&zombie->link); + list_add_tail(&zombie->link, &cell->sv_list); + afs_get_server(zombie); + afs_kafstimod_del_timer(&zombie->timeout); + spin_unlock(&cell->sv_gylock); + write_unlock(&cell->sv_lock); + + kfree(server); + + *_server = zombie; + _leave(" = 0 (%p)", zombie); + return 0; + +} /* end afs_server_lookup() */ + +/*****************************************************************************/ +/* + * destroy a server record + * - removes from the cell list + */ +void afs_put_server(struct afs_server *server) +{ + struct afs_cell *cell; + + if (!server) + return; + + _enter("%p", server); + + cell = server->cell; + + /* sanity check */ + BUG_ON(atomic_read(&server->usage) <= 0); + + /* to prevent a race, the decrement and the dequeue must be effectively + * atomic */ + write_lock(&cell->sv_lock); + + if (likely(!atomic_dec_and_test(&server->usage))) { + write_unlock(&cell->sv_lock); + _leave(""); + return; + } + + spin_lock(&cell->sv_gylock); + list_del(&server->link); + list_add_tail(&server->link, &cell->sv_graveyard); + + /* time out in 10 secs */ + afs_kafstimod_add_timer(&server->timeout, 10 * HZ); + + spin_unlock(&cell->sv_gylock); + write_unlock(&cell->sv_lock); + + _leave(" [killed]"); +} /* end afs_put_server() */ + +/*****************************************************************************/ +/* + * timeout server record + * - removes from the cell's graveyard if the usage count is zero + */ +void afs_server_do_timeout(struct afs_server *server) +{ + struct rxrpc_peer *peer; + struct afs_cell *cell; + int loop; + + _enter("%p", server); + + cell = server->cell; + + BUG_ON(atomic_read(&server->usage) < 0); + + /* remove from graveyard if still dead */ + spin_lock(&cell->vl_gylock); + if (atomic_read(&server->usage) == 0) + list_del_init(&server->link); + else + server = NULL; + spin_unlock(&cell->vl_gylock); + + if (!server) { + _leave(""); + return; /* resurrected */ + } + + /* we can now destroy it properly */ + afs_put_cell(cell); + + /* uncross-point the structs under a global lock */ + spin_lock(&afs_server_peer_lock); + peer = server->peer; + if (peer) { + server->peer = NULL; + peer->user = NULL; + } + spin_unlock(&afs_server_peer_lock); + + /* finish cleaning up the server */ + for (loop = AFS_SERVER_CONN_LIST_SIZE - 1; loop >= 0; loop--) + if (server->fs_conn[loop]) + rxrpc_put_connection(server->fs_conn[loop]); + + if (server->vlserver) + rxrpc_put_connection(server->vlserver); + + kfree(server); + + _leave(" [destroyed]"); +} /* end afs_server_do_timeout() */ + +/*****************************************************************************/ +/* + * get a callslot on a connection to the fileserver on the specified server + */ +int afs_server_request_callslot(struct afs_server *server, + struct afs_server_callslot *callslot) +{ + struct afs_server_callslot *pcallslot; + struct rxrpc_connection *conn; + int nconn, ret; + + _enter("%p,",server); + + INIT_LIST_HEAD(&callslot->link); + callslot->task = current; + callslot->conn = NULL; + callslot->nconn = -1; + callslot->ready = 0; + + ret = 0; + conn = NULL; + + /* get hold of a callslot first */ + spin_lock(&server->fs_lock); + + /* resurrect the server if it's death timeout has expired */ + if (server->fs_state) { + if (time_before(jiffies, server->fs_dead_jif)) { + ret = server->fs_state; + spin_unlock(&server->fs_lock); + _leave(" = %d [still dead]", ret); + return ret; + } + + server->fs_state = 0; + } + + /* try and find a connection that has spare callslots */ + for (nconn = 0; nconn < AFS_SERVER_CONN_LIST_SIZE; nconn++) { + if (server->fs_conn_cnt[nconn] > 0) { + server->fs_conn_cnt[nconn]--; + spin_unlock(&server->fs_lock); + callslot->nconn = nconn; + goto obtained_slot; + } + } + + /* none were available - wait interruptibly for one to become + * available */ + set_current_state(TASK_INTERRUPTIBLE); + list_add_tail(&callslot->link, &server->fs_callq); + spin_unlock(&server->fs_lock); + + while (!callslot->ready && !signal_pending(current)) { + schedule(); + set_current_state(TASK_INTERRUPTIBLE); + } + + set_current_state(TASK_RUNNING); + + /* even if we were interrupted we may still be queued */ + if (!callslot->ready) { + spin_lock(&server->fs_lock); + list_del_init(&callslot->link); + spin_unlock(&server->fs_lock); + } + + nconn = callslot->nconn; + + /* if interrupted, we must release any slot we also got before + * returning an error */ + if (signal_pending(current)) { + ret = -EINTR; + goto error_release; + } + + /* if we were woken up with an error, then pass that error back to the + * called */ + if (nconn < 0) { + _leave(" = %d", callslot->errno); + return callslot->errno; + } + + /* were we given a connection directly? */ + if (callslot->conn) { + /* yes - use it */ + _leave(" = 0 (nc=%d)", nconn); + return 0; + } + + /* got a callslot, but no connection */ + obtained_slot: + + /* need to get hold of the RxRPC connection */ + down_write(&server->sem); + + /* quick check to see if there's an outstanding error */ + ret = server->fs_state; + if (ret) + goto error_release_upw; + + if (server->fs_conn[nconn]) { + /* reuse an existing connection */ + rxrpc_get_connection(server->fs_conn[nconn]); + callslot->conn = server->fs_conn[nconn]; + } + else { + /* create a new connection */ + ret = rxrpc_create_connection(afs_transport, + htons(7000), + server->addr.s_addr, + FS_SERVICE_ID, + NULL, + &server->fs_conn[nconn]); + + if (ret < 0) + goto error_release_upw; + + callslot->conn = server->fs_conn[0]; + rxrpc_get_connection(callslot->conn); + } + + up_write(&server->sem); + + _leave(" = 0"); + return 0; + + /* handle an error occurring */ + error_release_upw: + up_write(&server->sem); + + error_release: + /* either release the callslot or pass it along to another deserving + * task */ + spin_lock(&server->fs_lock); + + if (nconn < 0) { + /* no callslot allocated */ + } + else if (list_empty(&server->fs_callq)) { + /* no one waiting */ + server->fs_conn_cnt[nconn]++; + spin_unlock(&server->fs_lock); + } + else { + /* someone's waiting - dequeue them and wake them up */ + pcallslot = list_entry(server->fs_callq.next, + struct afs_server_callslot, link); + list_del_init(&pcallslot->link); + + pcallslot->errno = server->fs_state; + if (!pcallslot->errno) { + /* pass them out callslot details */ + callslot->conn = xchg(&pcallslot->conn, + callslot->conn); + pcallslot->nconn = nconn; + callslot->nconn = nconn = -1; + } + pcallslot->ready = 1; + wake_up_process(pcallslot->task); + spin_unlock(&server->fs_lock); + } + + rxrpc_put_connection(callslot->conn); + callslot->conn = NULL; + + _leave(" = %d", ret); + return ret; + +} /* end afs_server_request_callslot() */ + +/*****************************************************************************/ +/* + * release a callslot back to the server + * - transfers the RxRPC connection to the next pending callslot if possible + */ +void afs_server_release_callslot(struct afs_server *server, + struct afs_server_callslot *callslot) +{ + struct afs_server_callslot *pcallslot; + + _enter("{ad=%08x,cnt=%u},{%d}", + ntohl(server->addr.s_addr), + server->fs_conn_cnt[callslot->nconn], + callslot->nconn); + + BUG_ON(callslot->nconn < 0); + + spin_lock(&server->fs_lock); + + if (list_empty(&server->fs_callq)) { + /* no one waiting */ + server->fs_conn_cnt[callslot->nconn]++; + spin_unlock(&server->fs_lock); + } + else { + /* someone's waiting - dequeue them and wake them up */ + pcallslot = list_entry(server->fs_callq.next, + struct afs_server_callslot, link); + list_del_init(&pcallslot->link); + + pcallslot->errno = server->fs_state; + if (!pcallslot->errno) { + /* pass them out callslot details */ + callslot->conn = xchg(&pcallslot->conn, callslot->conn); + pcallslot->nconn = callslot->nconn; + callslot->nconn = -1; + } + + pcallslot->ready = 1; + wake_up_process(pcallslot->task); + spin_unlock(&server->fs_lock); + } + + rxrpc_put_connection(callslot->conn); + + _leave(""); +} /* end afs_server_release_callslot() */ + +/*****************************************************************************/ +/* + * get a handle to a connection to the vlserver (volume location) on the + * specified server + */ +int afs_server_get_vlconn(struct afs_server *server, + struct rxrpc_connection **_conn) +{ + struct rxrpc_connection *conn; + int ret; + + _enter("%p,", server); + + ret = 0; + conn = NULL; + down_read(&server->sem); + + if (server->vlserver) { + /* reuse an existing connection */ + rxrpc_get_connection(server->vlserver); + conn = server->vlserver; + up_read(&server->sem); + } + else { + /* create a new connection */ + up_read(&server->sem); + down_write(&server->sem); + if (!server->vlserver) { + ret = rxrpc_create_connection(afs_transport, + htons(7003), + server->addr.s_addr, + VL_SERVICE_ID, + NULL, + &server->vlserver); + } + if (ret == 0) { + rxrpc_get_connection(server->vlserver); + conn = server->vlserver; + } + up_write(&server->sem); + } + + *_conn = conn; + _leave(" = %d", ret); + return ret; +} /* end afs_server_get_vlconn() */ diff --git a/fs/afs/server.h b/fs/afs/server.h new file mode 100644 index 00000000000..c3d24115578 --- /dev/null +++ b/fs/afs/server.h @@ -0,0 +1,102 @@ +/* server.h: AFS server record + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_SERVER_H +#define _LINUX_AFS_SERVER_H + +#include "types.h" +#include "kafstimod.h" +#include <rxrpc/peer.h> +#include <linux/rwsem.h> + +extern spinlock_t afs_server_peer_lock; + +/*****************************************************************************/ +/* + * AFS server record + */ +struct afs_server +{ + atomic_t usage; + struct afs_cell *cell; /* cell in which server resides */ + struct list_head link; /* link in cell's server list */ + struct rw_semaphore sem; /* access lock */ + struct afs_timer timeout; /* graveyard timeout */ + struct in_addr addr; /* server address */ + struct rxrpc_peer *peer; /* peer record for this server */ + struct rxrpc_connection *vlserver; /* connection to the volume location service */ + + /* file service access */ +#define AFS_SERVER_CONN_LIST_SIZE 2 + struct rxrpc_connection *fs_conn[AFS_SERVER_CONN_LIST_SIZE]; /* FS connections */ + unsigned fs_conn_cnt[AFS_SERVER_CONN_LIST_SIZE]; /* per conn call count */ + struct list_head fs_callq; /* queue of processes waiting to make a call */ + spinlock_t fs_lock; /* access lock */ + int fs_state; /* 0 or reason FS currently marked dead (-errno) */ + unsigned fs_rtt; /* FS round trip time */ + unsigned long fs_act_jif; /* time at which last activity occurred */ + unsigned long fs_dead_jif; /* time at which no longer to be considered dead */ + + /* callback promise management */ + struct list_head cb_promises; /* as yet unbroken promises from this server */ + spinlock_t cb_lock; /* access lock */ +}; + +extern int afs_server_lookup(struct afs_cell *cell, + const struct in_addr *addr, + struct afs_server **_server); + +#define afs_get_server(S) do { atomic_inc(&(S)->usage); } while(0) + +extern void afs_put_server(struct afs_server *server); +extern void afs_server_do_timeout(struct afs_server *server); + +extern int afs_server_find_by_peer(const struct rxrpc_peer *peer, + struct afs_server **_server); + +extern int afs_server_get_vlconn(struct afs_server *server, + struct rxrpc_connection **_conn); + +static inline +struct afs_server *afs_server_get_from_peer(struct rxrpc_peer *peer) +{ + struct afs_server *server; + + spin_lock(&afs_server_peer_lock); + server = peer->user; + if (server) + afs_get_server(server); + spin_unlock(&afs_server_peer_lock); + + return server; +} + +/*****************************************************************************/ +/* + * AFS server callslot grant record + */ +struct afs_server_callslot +{ + struct list_head link; /* link in server's list */ + struct task_struct *task; /* process waiting to make call */ + struct rxrpc_connection *conn; /* connection to use (or NULL on error) */ + short nconn; /* connection slot number (-1 on error) */ + char ready; /* T when ready */ + int errno; /* error number if nconn==-1 */ +}; + +extern int afs_server_request_callslot(struct afs_server *server, + struct afs_server_callslot *callslot); + +extern void afs_server_release_callslot(struct afs_server *server, + struct afs_server_callslot *callslot); + +#endif /* _LINUX_AFS_SERVER_H */ diff --git a/fs/afs/super.c b/fs/afs/super.c new file mode 100644 index 00000000000..d6fa8e5999d --- /dev/null +++ b/fs/afs/super.c @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2002 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 Howells <dhowells@redhat.com> + * David Woodhouse <dwmw2@cambridge.redhat.com> + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include "vnode.h" +#include "volume.h" +#include "cell.h" +#include "cmservice.h" +#include "fsclient.h" +#include "super.h" +#include "internal.h" + +#define AFS_FS_MAGIC 0x6B414653 /* 'kAFS' */ + +struct afs_mount_params { + int rwpath; + struct afs_cell *default_cell; + struct afs_volume *volume; +}; + +static void afs_i_init_once(void *foo, kmem_cache_t *cachep, + unsigned long flags); + +static struct super_block *afs_get_sb(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *data); + +static struct inode *afs_alloc_inode(struct super_block *sb); + +static void afs_put_super(struct super_block *sb); + +static void afs_destroy_inode(struct inode *inode); + +static struct file_system_type afs_fs_type = { + .owner = THIS_MODULE, + .name = "afs", + .get_sb = afs_get_sb, + .kill_sb = kill_anon_super, + .fs_flags = FS_BINARY_MOUNTDATA, +}; + +static struct super_operations afs_super_ops = { + .statfs = simple_statfs, + .alloc_inode = afs_alloc_inode, + .drop_inode = generic_delete_inode, + .destroy_inode = afs_destroy_inode, + .clear_inode = afs_clear_inode, + .put_super = afs_put_super, +}; + +static kmem_cache_t *afs_inode_cachep; +static atomic_t afs_count_active_inodes; + +/*****************************************************************************/ +/* + * initialise the filesystem + */ +int __init afs_fs_init(void) +{ + int ret; + + _enter(""); + + afs_timer_init(&afs_mntpt_expiry_timer, &afs_mntpt_expiry_timer_ops); + + /* create ourselves an inode cache */ + atomic_set(&afs_count_active_inodes, 0); + + ret = -ENOMEM; + afs_inode_cachep = kmem_cache_create("afs_inode_cache", + sizeof(struct afs_vnode), + 0, + SLAB_HWCACHE_ALIGN, + afs_i_init_once, + NULL); + if (!afs_inode_cachep) { + printk(KERN_NOTICE "kAFS: Failed to allocate inode cache\n"); + return ret; + } + + /* now export our filesystem to lesser mortals */ + ret = register_filesystem(&afs_fs_type); + if (ret < 0) { + kmem_cache_destroy(afs_inode_cachep); + kleave(" = %d", ret); + return ret; + } + + kleave(" = 0"); + return 0; +} /* end afs_fs_init() */ + +/*****************************************************************************/ +/* + * clean up the filesystem + */ +void __exit afs_fs_exit(void) +{ + unregister_filesystem(&afs_fs_type); + + if (atomic_read(&afs_count_active_inodes) != 0) { + printk("kAFS: %d active inode objects still present\n", + atomic_read(&afs_count_active_inodes)); + BUG(); + } + + kmem_cache_destroy(afs_inode_cachep); + +} /* end afs_fs_exit() */ + +/*****************************************************************************/ +/* + * check that an argument has a value + */ +static int want_arg(char **_value, const char *option) +{ + if (!_value || !*_value || !**_value) { + printk(KERN_NOTICE "kAFS: %s: argument missing\n", option); + return 0; + } + return 1; +} /* end want_arg() */ + +/*****************************************************************************/ +/* + * check that there's no subsequent value + */ +static int want_no_value(char *const *_value, const char *option) +{ + if (*_value && **_value) { + printk(KERN_NOTICE "kAFS: %s: Invalid argument: %s\n", + option, *_value); + return 0; + } + return 1; +} /* end want_no_value() */ + +/*****************************************************************************/ +/* + * parse the mount options + * - this function has been shamelessly adapted from the ext3 fs which + * shamelessly adapted it from the msdos fs + */ +static int afs_super_parse_options(struct afs_mount_params *params, + char *options, + const char **devname) +{ + char *key, *value; + int ret; + + _enter("%s", options); + + options[PAGE_SIZE - 1] = 0; + + ret = 0; + while ((key = strsep(&options, ",")) != 0) + { + value = strchr(key, '='); + if (value) + *value++ = 0; + + printk("kAFS: KEY: %s, VAL:%s\n", key, value ?: "-"); + + if (strcmp(key, "rwpath") == 0) { + if (!want_no_value(&value, "rwpath")) + return -EINVAL; + params->rwpath = 1; + continue; + } + else if (strcmp(key, "vol") == 0) { + if (!want_arg(&value, "vol")) + return -EINVAL; + *devname = value; + continue; + } + else if (strcmp(key, "cell") == 0) { + if (!want_arg(&value, "cell")) + return -EINVAL; + afs_put_cell(params->default_cell); + ret = afs_cell_lookup(value, + strlen(value), + ¶ms->default_cell); + if (ret < 0) + return -EINVAL; + continue; + } + + printk("kAFS: Unknown mount option: '%s'\n", key); + ret = -EINVAL; + goto error; + } + + ret = 0; + + error: + _leave(" = %d", ret); + return ret; +} /* end afs_super_parse_options() */ + +/*****************************************************************************/ +/* + * check a superblock to see if it's the one we're looking for + */ +static int afs_test_super(struct super_block *sb, void *data) +{ + struct afs_mount_params *params = data; + struct afs_super_info *as = sb->s_fs_info; + + return as->volume == params->volume; +} /* end afs_test_super() */ + +/*****************************************************************************/ +/* + * fill in the superblock + */ +static int afs_fill_super(struct super_block *sb, void *data, int silent) +{ + struct afs_mount_params *params = data; + struct afs_super_info *as = NULL; + struct afs_fid fid; + struct dentry *root = NULL; + struct inode *inode = NULL; + int ret; + + kenter(""); + + /* allocate a superblock info record */ + as = kmalloc(sizeof(struct afs_super_info), GFP_KERNEL); + if (!as) { + _leave(" = -ENOMEM"); + return -ENOMEM; + } + + memset(as, 0, sizeof(struct afs_super_info)); + + afs_get_volume(params->volume); + as->volume = params->volume; + + /* fill in the superblock */ + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = AFS_FS_MAGIC; + sb->s_op = &afs_super_ops; + sb->s_fs_info = as; + + /* allocate the root inode and dentry */ + fid.vid = as->volume->vid; + fid.vnode = 1; + fid.unique = 1; + ret = afs_iget(sb, &fid, &inode); + if (ret < 0) + goto error; + + ret = -ENOMEM; + root = d_alloc_root(inode); + if (!root) + goto error; + + sb->s_root = root; + + kleave(" = 0"); + return 0; + + error: + iput(inode); + afs_put_volume(as->volume); + kfree(as); + + sb->s_fs_info = NULL; + + kleave(" = %d", ret); + return ret; +} /* end afs_fill_super() */ + +/*****************************************************************************/ +/* + * get an AFS superblock + * - TODO: don't use get_sb_nodev(), but rather call sget() directly + */ +static struct super_block *afs_get_sb(struct file_system_type *fs_type, + int flags, + const char *dev_name, + void *options) +{ + struct afs_mount_params params; + struct super_block *sb; + int ret; + + _enter(",,%s,%p", dev_name, options); + + memset(¶ms, 0, sizeof(params)); + + /* start the cache manager */ + ret = afscm_start(); + if (ret < 0) { + _leave(" = %d", ret); + return ERR_PTR(ret); + } + + /* parse the options */ + if (options) { + ret = afs_super_parse_options(¶ms, options, &dev_name); + if (ret < 0) + goto error; + if (!dev_name) { + printk("kAFS: no volume name specified\n"); + ret = -EINVAL; + goto error; + } + } + + /* parse the device name */ + ret = afs_volume_lookup(dev_name, + params.default_cell, + params.rwpath, + ¶ms.volume); + if (ret < 0) + goto error; + + /* allocate a deviceless superblock */ + sb = sget(fs_type, afs_test_super, set_anon_super, ¶ms); + if (IS_ERR(sb)) + goto error; + + sb->s_flags = flags; + + ret = afs_fill_super(sb, ¶ms, flags & MS_VERBOSE ? 1 : 0); + if (ret < 0) { + up_write(&sb->s_umount); + deactivate_super(sb); + goto error; + } + sb->s_flags |= MS_ACTIVE; + + afs_put_volume(params.volume); + afs_put_cell(params.default_cell); + _leave(" = %p", sb); + return sb; + + error: + afs_put_volume(params.volume); + afs_put_cell(params.default_cell); + afscm_stop(); + _leave(" = %d", ret); + return ERR_PTR(ret); +} /* end afs_get_sb() */ + +/*****************************************************************************/ +/* + * finish the unmounting process on the superblock + */ +static void afs_put_super(struct super_block *sb) +{ + struct afs_super_info *as = sb->s_fs_info; + + _enter(""); + + afs_put_volume(as->volume); + afscm_stop(); + + _leave(""); +} /* end afs_put_super() */ + +/*****************************************************************************/ +/* + * initialise an inode cache slab element prior to any use + */ +static void afs_i_init_once(void *_vnode, kmem_cache_t *cachep, + unsigned long flags) +{ + struct afs_vnode *vnode = (struct afs_vnode *) _vnode; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) { + memset(vnode, 0, sizeof(*vnode)); + inode_init_once(&vnode->vfs_inode); + init_waitqueue_head(&vnode->update_waitq); + spin_lock_init(&vnode->lock); + INIT_LIST_HEAD(&vnode->cb_link); + INIT_LIST_HEAD(&vnode->cb_hash_link); + afs_timer_init(&vnode->cb_timeout, + &afs_vnode_cb_timed_out_ops); + } + +} /* end afs_i_init_once() */ + +/*****************************************************************************/ +/* + * allocate an AFS inode struct from our slab cache + */ +static struct inode *afs_alloc_inode(struct super_block *sb) +{ + struct afs_vnode *vnode; + + vnode = (struct afs_vnode *) + kmem_cache_alloc(afs_inode_cachep, SLAB_KERNEL); + if (!vnode) + return NULL; + + atomic_inc(&afs_count_active_inodes); + + memset(&vnode->fid, 0, sizeof(vnode->fid)); + memset(&vnode->status, 0, sizeof(vnode->status)); + + vnode->volume = NULL; + vnode->update_cnt = 0; + vnode->flags = 0; + + return &vnode->vfs_inode; +} /* end afs_alloc_inode() */ + +/*****************************************************************************/ +/* + * destroy an AFS inode struct + */ +static void afs_destroy_inode(struct inode *inode) +{ + _enter("{%lu}", inode->i_ino); + + kmem_cache_free(afs_inode_cachep, AFS_FS_I(inode)); + + atomic_dec(&afs_count_active_inodes); + +} /* end afs_destroy_inode() */ diff --git a/fs/afs/super.h b/fs/afs/super.h new file mode 100644 index 00000000000..ac11362f4e9 --- /dev/null +++ b/fs/afs/super.h @@ -0,0 +1,43 @@ +/* super.h: AFS filesystem internal private data + * + * Copyright (c) 2002 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@cambridge.redhat.com> + * David Howells <dhowells@redhat.com> + * + */ + +#ifndef _LINUX_AFS_SUPER_H +#define _LINUX_AFS_SUPER_H + +#include <linux/fs.h> +#include "server.h" + +#ifdef __KERNEL__ + +/*****************************************************************************/ +/* + * AFS superblock private data + * - there's one superblock per volume + */ +struct afs_super_info +{ + struct afs_volume *volume; /* volume record */ + char rwparent; /* T if parent is R/W AFS volume */ +}; + +static inline struct afs_super_info *AFS_FS_S(struct super_block *sb) +{ + return sb->s_fs_info; +} + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_AFS_SUPER_H */ diff --git a/fs/afs/transport.h b/fs/afs/transport.h new file mode 100644 index 00000000000..7013ae6ccc8 --- /dev/null +++ b/fs/afs/transport.h @@ -0,0 +1,21 @@ +/* transport.h: AFS transport management + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_TRANSPORT_H +#define _LINUX_AFS_TRANSPORT_H + +#include "types.h" +#include <rxrpc/transport.h> + +/* the cache manager transport endpoint */ +extern struct rxrpc_transport *afs_transport; + +#endif /* _LINUX_AFS_TRANSPORT_H */ diff --git a/fs/afs/types.h b/fs/afs/types.h new file mode 100644 index 00000000000..b1a2367c758 --- /dev/null +++ b/fs/afs/types.h @@ -0,0 +1,125 @@ +/* types.h: AFS types + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_TYPES_H +#define _LINUX_AFS_TYPES_H + +#ifdef __KERNEL__ +#include <rxrpc/types.h> +#endif /* __KERNEL__ */ + +typedef unsigned afs_volid_t; +typedef unsigned afs_vnodeid_t; +typedef unsigned long long afs_dataversion_t; + +typedef enum { + AFSVL_RWVOL, /* read/write volume */ + AFSVL_ROVOL, /* read-only volume */ + AFSVL_BACKVOL, /* backup volume */ +} __attribute__((packed)) afs_voltype_t; + +typedef enum { + AFS_FTYPE_INVALID = 0, + AFS_FTYPE_FILE = 1, + AFS_FTYPE_DIR = 2, + AFS_FTYPE_SYMLINK = 3, +} afs_file_type_t; + +#ifdef __KERNEL__ + +struct afs_cell; +struct afs_vnode; + +/*****************************************************************************/ +/* + * AFS file identifier + */ +struct afs_fid +{ + afs_volid_t vid; /* volume ID */ + afs_vnodeid_t vnode; /* file index within volume */ + unsigned unique; /* unique ID number (file index version) */ +}; + +/*****************************************************************************/ +/* + * AFS callback notification + */ +typedef enum { + AFSCM_CB_UNTYPED = 0, /* no type set on CB break */ + AFSCM_CB_EXCLUSIVE = 1, /* CB exclusive to CM [not implemented] */ + AFSCM_CB_SHARED = 2, /* CB shared by other CM's */ + AFSCM_CB_DROPPED = 3, /* CB promise cancelled by file server */ +} afs_callback_type_t; + +struct afs_callback +{ + struct afs_server *server; /* server that made the promise */ + struct afs_fid fid; /* file identifier */ + unsigned version; /* callback version */ + unsigned expiry; /* time at which expires */ + afs_callback_type_t type; /* type of callback */ +}; + +#define AFSCBMAX 50 + +/*****************************************************************************/ +/* + * AFS volume information + */ +struct afs_volume_info +{ + afs_volid_t vid; /* volume ID */ + afs_voltype_t type; /* type of this volume */ + afs_volid_t type_vids[5]; /* volume ID's for possible types for this vol */ + + /* list of fileservers serving this volume */ + size_t nservers; /* number of entries used in servers[] */ + struct { + struct in_addr addr; /* fileserver address */ + } servers[8]; +}; + +/*****************************************************************************/ +/* + * AFS file status information + */ +struct afs_file_status +{ + unsigned if_version; /* interface version */ +#define AFS_FSTATUS_VERSION 1 + + afs_file_type_t type; /* file type */ + unsigned nlink; /* link count */ + size_t size; /* file size */ + afs_dataversion_t version; /* current data version */ + unsigned author; /* author ID */ + unsigned owner; /* owner ID */ + unsigned caller_access; /* access rights for authenticated caller */ + unsigned anon_access; /* access rights for unauthenticated caller */ + umode_t mode; /* UNIX mode */ + struct afs_fid parent; /* parent file ID */ + time_t mtime_client; /* last time client changed data */ + time_t mtime_server; /* last time server changed data */ +}; + +/*****************************************************************************/ +/* + * AFS volume synchronisation information + */ +struct afs_volsync +{ + time_t creation; /* volume creation time */ +}; + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_AFS_TYPES_H */ diff --git a/fs/afs/vlclient.c b/fs/afs/vlclient.c new file mode 100644 index 00000000000..7b0e3192ee3 --- /dev/null +++ b/fs/afs/vlclient.c @@ -0,0 +1,695 @@ +/* vlclient.c: AFS Volume Location Service client + * + * Copyright (C) 2002 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/init.h> +#include <linux/sched.h> +#include <rxrpc/rxrpc.h> +#include <rxrpc/transport.h> +#include <rxrpc/connection.h> +#include <rxrpc/call.h> +#include "server.h" +#include "volume.h" +#include "vlclient.h" +#include "kafsasyncd.h" +#include "kafstimod.h" +#include "errors.h" +#include "internal.h" + +#define VLGETENTRYBYID 503 /* AFS Get Cache Entry By ID operation ID */ +#define VLGETENTRYBYNAME 504 /* AFS Get Cache Entry By Name operation ID */ +#define VLPROBE 514 /* AFS Probe Volume Location Service operation ID */ + +static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call); +static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call); + +/*****************************************************************************/ +/* + * map afs VL abort codes to/from Linux error codes + * - called with call->lock held + */ +static void afs_rxvl_aemap(struct rxrpc_call *call) +{ + int err; + + _enter("{%u,%u,%d}", + call->app_err_state, call->app_abort_code, call->app_errno); + + switch (call->app_err_state) { + case RXRPC_ESTATE_LOCAL_ABORT: + call->app_abort_code = -call->app_errno; + return; + + case RXRPC_ESTATE_PEER_ABORT: + switch (call->app_abort_code) { + case AFSVL_IDEXIST: err = -EEXIST; break; + case AFSVL_IO: err = -EREMOTEIO; break; + case AFSVL_NAMEEXIST: err = -EEXIST; break; + case AFSVL_CREATEFAIL: err = -EREMOTEIO; break; + case AFSVL_NOENT: err = -ENOMEDIUM; break; + case AFSVL_EMPTY: err = -ENOMEDIUM; break; + case AFSVL_ENTDELETED: err = -ENOMEDIUM; break; + case AFSVL_BADNAME: err = -EINVAL; break; + case AFSVL_BADINDEX: err = -EINVAL; break; + case AFSVL_BADVOLTYPE: err = -EINVAL; break; + case AFSVL_BADSERVER: err = -EINVAL; break; + case AFSVL_BADPARTITION: err = -EINVAL; break; + case AFSVL_REPSFULL: err = -EFBIG; break; + case AFSVL_NOREPSERVER: err = -ENOENT; break; + case AFSVL_DUPREPSERVER: err = -EEXIST; break; + case AFSVL_RWNOTFOUND: err = -ENOENT; break; + case AFSVL_BADREFCOUNT: err = -EINVAL; break; + case AFSVL_SIZEEXCEEDED: err = -EINVAL; break; + case AFSVL_BADENTRY: err = -EINVAL; break; + case AFSVL_BADVOLIDBUMP: err = -EINVAL; break; + case AFSVL_IDALREADYHASHED: err = -EINVAL; break; + case AFSVL_ENTRYLOCKED: err = -EBUSY; break; + case AFSVL_BADVOLOPER: err = -EBADRQC; break; + case AFSVL_BADRELLOCKTYPE: err = -EINVAL; break; + case AFSVL_RERELEASE: err = -EREMOTEIO; break; + case AFSVL_BADSERVERFLAG: err = -EINVAL; break; + case AFSVL_PERM: err = -EACCES; break; + case AFSVL_NOMEM: err = -EREMOTEIO; break; + default: + err = afs_abort_to_error(call->app_abort_code); + break; + } + call->app_errno = err; + return; + + default: + return; + } +} /* end afs_rxvl_aemap() */ + +#if 0 +/*****************************************************************************/ +/* + * probe a volume location server to see if it is still alive -- unused + */ +static int afs_rxvl_probe(struct afs_server *server, int alloc_flags) +{ + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[1]; + size_t sent; + int ret; + __be32 param[1]; + + DECLARE_WAITQUEUE(myself, current); + + /* get hold of the vlserver connection */ + ret = afs_server_get_vlconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = VLPROBE; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + param[0] = htonl(VLPROBE); + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, + alloc_flags, 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY || + signal_pending(current)) + break; + schedule(); + } + set_current_state(TASK_RUNNING); + + ret = -EINTR; + if (signal_pending(current)) + goto abort; + + switch (call->app_call_state) { + case RXRPC_CSTATE_ERROR: + ret = call->app_errno; + goto out_unwait; + + case RXRPC_CSTATE_CLNT_GOT_REPLY: + ret = 0; + goto out_unwait; + + default: + BUG(); + } + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + rxrpc_put_connection(conn); + out: + return ret; + +} /* end afs_rxvl_probe() */ +#endif + +/*****************************************************************************/ +/* + * look up a volume location database entry by name + */ +int afs_rxvl_get_entry_by_name(struct afs_server *server, + const char *volname, + unsigned volnamesz, + struct afs_cache_vlocation *entry) +{ + DECLARE_WAITQUEUE(myself, current); + + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[3]; + unsigned tmp; + size_t sent; + int ret, loop; + __be32 *bp, param[2], zero; + + _enter(",%*.*s,%u,", volnamesz, volnamesz, volname, volnamesz); + + memset(entry, 0, sizeof(*entry)); + + /* get hold of the vlserver connection */ + ret = afs_server_get_vlconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = VLGETENTRYBYNAME; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + piov[1].iov_len = volnamesz; + piov[1].iov_base = (char *) volname; + + zero = 0; + piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3; + piov[2].iov_base = &zero; + + param[0] = htonl(VLGETENTRYBYNAME); + param[1] = htonl(piov[1].iov_len); + + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + bp = rxrpc_call_alloc_scratch(call, 384); + + ret = rxrpc_call_read_data(call, bp, 384, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) { + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + goto abort; + } + + /* unmarshall the reply */ + for (loop = 0; loop < 64; loop++) + entry->name[loop] = ntohl(*bp++); + bp++; /* final NUL */ + + bp++; /* type */ + entry->nservers = ntohl(*bp++); + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].s_addr = *bp++; + + bp += 8; /* partition IDs */ + + for (loop = 0; loop < 8; loop++) { + tmp = ntohl(*bp++); + if (tmp & AFS_VLSF_RWVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLSF_ROVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLSF_BACKVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_BAK; + } + + entry->vid[0] = ntohl(*bp++); + entry->vid[1] = ntohl(*bp++); + entry->vid[2] = ntohl(*bp++); + + bp++; /* clone ID */ + + tmp = ntohl(*bp++); /* flags */ + if (tmp & AFS_VLF_RWEXISTS) + entry->vidmask |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLF_ROEXISTS) + entry->vidmask |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLF_BACKEXISTS) + entry->vidmask |= AFS_VOL_VTM_BAK; + + ret = -ENOMEDIUM; + if (!entry->vidmask) + goto abort; + + /* success */ + entry->rtime = get_seconds(); + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + rxrpc_put_connection(conn); + out: + _leave(" = %d", ret); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; +} /* end afs_rxvl_get_entry_by_name() */ + +/*****************************************************************************/ +/* + * look up a volume location database entry by ID + */ +int afs_rxvl_get_entry_by_id(struct afs_server *server, + afs_volid_t volid, + afs_voltype_t voltype, + struct afs_cache_vlocation *entry) +{ + DECLARE_WAITQUEUE(myself, current); + + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[1]; + unsigned tmp; + size_t sent; + int ret, loop; + __be32 *bp, param[3]; + + _enter(",%x,%d,", volid, voltype); + + memset(entry, 0, sizeof(*entry)); + + /* get hold of the vlserver connection */ + ret = afs_server_get_vlconn(server, &conn); + if (ret < 0) + goto out; + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call); + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + goto out_put_conn; + } + call->app_opcode = VLGETENTRYBYID; + + /* we want to get event notifications from the call */ + add_wait_queue(&call->waitq, &myself); + + /* marshall the parameters */ + param[0] = htonl(VLGETENTRYBYID); + param[1] = htonl(volid); + param[2] = htonl(voltype); + + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) + goto abort; + + /* wait for the reply to completely arrive */ + bp = rxrpc_call_alloc_scratch(call, 384); + + ret = rxrpc_call_read_data(call, bp, 384, + RXRPC_CALL_READ_BLOCK | + RXRPC_CALL_READ_ALL); + if (ret < 0) { + if (ret == -ECONNABORTED) { + ret = call->app_errno; + goto out_unwait; + } + goto abort; + } + + /* unmarshall the reply */ + for (loop = 0; loop < 64; loop++) + entry->name[loop] = ntohl(*bp++); + bp++; /* final NUL */ + + bp++; /* type */ + entry->nservers = ntohl(*bp++); + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].s_addr = *bp++; + + bp += 8; /* partition IDs */ + + for (loop = 0; loop < 8; loop++) { + tmp = ntohl(*bp++); + if (tmp & AFS_VLSF_RWVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLSF_ROVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLSF_BACKVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_BAK; + } + + entry->vid[0] = ntohl(*bp++); + entry->vid[1] = ntohl(*bp++); + entry->vid[2] = ntohl(*bp++); + + bp++; /* clone ID */ + + tmp = ntohl(*bp++); /* flags */ + if (tmp & AFS_VLF_RWEXISTS) + entry->vidmask |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLF_ROEXISTS) + entry->vidmask |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLF_BACKEXISTS) + entry->vidmask |= AFS_VOL_VTM_BAK; + + ret = -ENOMEDIUM; + if (!entry->vidmask) + goto abort; + +#if 0 /* TODO: remove */ + entry->nservers = 3; + entry->servers[0].s_addr = htonl(0xac101249); + entry->servers[1].s_addr = htonl(0xac101243); + entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/); + + entry->srvtmask[0] = AFS_VOL_VTM_RO; + entry->srvtmask[1] = AFS_VOL_VTM_RO; + entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW; +#endif + + /* success */ + entry->rtime = get_seconds(); + ret = 0; + + out_unwait: + set_current_state(TASK_RUNNING); + remove_wait_queue(&call->waitq, &myself); + rxrpc_put_call(call); + out_put_conn: + rxrpc_put_connection(conn); + out: + _leave(" = %d", ret); + return ret; + + abort: + set_current_state(TASK_UNINTERRUPTIBLE); + rxrpc_call_abort(call, ret); + schedule(); + goto out_unwait; +} /* end afs_rxvl_get_entry_by_id() */ + +/*****************************************************************************/ +/* + * look up a volume location database entry by ID asynchronously + */ +int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op, + afs_volid_t volid, + afs_voltype_t voltype) +{ + struct rxrpc_connection *conn; + struct rxrpc_call *call; + struct kvec piov[1]; + size_t sent; + int ret; + __be32 param[3]; + + _enter(",%x,%d,", volid, voltype); + + /* get hold of the vlserver connection */ + ret = afs_server_get_vlconn(op->server, &conn); + if (ret < 0) { + _leave(" = %d", ret); + return ret; + } + + /* create a call through that connection */ + ret = rxrpc_create_call(conn, + afs_rxvl_get_entry_by_id_attn, + afs_rxvl_get_entry_by_id_error, + afs_rxvl_aemap, + &op->call); + rxrpc_put_connection(conn); + + if (ret < 0) { + printk("kAFS: Unable to create call: %d\n", ret); + _leave(" = %d", ret); + return ret; + } + + op->call->app_opcode = VLGETENTRYBYID; + op->call->app_user = op; + + call = op->call; + rxrpc_get_call(call); + + /* send event notifications from the call to kafsasyncd */ + afs_kafsasyncd_begin_op(op); + + /* marshall the parameters */ + param[0] = htonl(VLGETENTRYBYID); + param[1] = htonl(volid); + param[2] = htonl(voltype); + + piov[0].iov_len = sizeof(param); + piov[0].iov_base = param; + + /* allocate result read buffer in scratch space */ + call->app_scr_ptr = rxrpc_call_alloc_scratch(op->call, 384); + + /* send the parameters to the server */ + ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS, + 0, &sent); + if (ret < 0) { + rxrpc_call_abort(call, ret); /* handle from kafsasyncd */ + ret = 0; + goto out; + } + + /* wait for the reply to completely arrive */ + ret = rxrpc_call_read_data(call, call->app_scr_ptr, 384, 0); + switch (ret) { + case 0: + case -EAGAIN: + case -ECONNABORTED: + ret = 0; + break; /* all handled by kafsasyncd */ + + default: + rxrpc_call_abort(call, ret); /* make kafsasyncd handle it */ + ret = 0; + break; + } + + out: + rxrpc_put_call(call); + _leave(" = %d", ret); + return ret; + +} /* end afs_rxvl_get_entry_by_id_async() */ + +/*****************************************************************************/ +/* + * attend to the asynchronous get VLDB entry by ID + */ +int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op, + struct afs_cache_vlocation *entry) +{ + __be32 *bp; + __u32 tmp; + int loop, ret; + + _enter("{op=%p cst=%u}", op, op->call->app_call_state); + + memset(entry, 0, sizeof(*entry)); + + if (op->call->app_call_state == RXRPC_CSTATE_COMPLETE) { + /* operation finished */ + afs_kafsasyncd_terminate_op(op); + + bp = op->call->app_scr_ptr; + + /* unmarshall the reply */ + for (loop = 0; loop < 64; loop++) + entry->name[loop] = ntohl(*bp++); + bp++; /* final NUL */ + + bp++; /* type */ + entry->nservers = ntohl(*bp++); + + for (loop = 0; loop < 8; loop++) + entry->servers[loop].s_addr = *bp++; + + bp += 8; /* partition IDs */ + + for (loop = 0; loop < 8; loop++) { + tmp = ntohl(*bp++); + if (tmp & AFS_VLSF_RWVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLSF_ROVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLSF_BACKVOL) + entry->srvtmask[loop] |= AFS_VOL_VTM_BAK; + } + + entry->vid[0] = ntohl(*bp++); + entry->vid[1] = ntohl(*bp++); + entry->vid[2] = ntohl(*bp++); + + bp++; /* clone ID */ + + tmp = ntohl(*bp++); /* flags */ + if (tmp & AFS_VLF_RWEXISTS) + entry->vidmask |= AFS_VOL_VTM_RW; + if (tmp & AFS_VLF_ROEXISTS) + entry->vidmask |= AFS_VOL_VTM_RO; + if (tmp & AFS_VLF_BACKEXISTS) + entry->vidmask |= AFS_VOL_VTM_BAK; + + ret = -ENOMEDIUM; + if (!entry->vidmask) { + rxrpc_call_abort(op->call, ret); + goto done; + } + +#if 0 /* TODO: remove */ + entry->nservers = 3; + entry->servers[0].s_addr = htonl(0xac101249); + entry->servers[1].s_addr = htonl(0xac101243); + entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/); + + entry->srvtmask[0] = AFS_VOL_VTM_RO; + entry->srvtmask[1] = AFS_VOL_VTM_RO; + entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW; +#endif + + /* success */ + entry->rtime = get_seconds(); + ret = 0; + goto done; + } + + if (op->call->app_call_state == RXRPC_CSTATE_ERROR) { + /* operation error */ + ret = op->call->app_errno; + goto done; + } + + _leave(" = -EAGAIN"); + return -EAGAIN; + + done: + rxrpc_put_call(op->call); + op->call = NULL; + _leave(" = %d", ret); + return ret; +} /* end afs_rxvl_get_entry_by_id_async2() */ + +/*****************************************************************************/ +/* + * handle attention events on an async get-entry-by-ID op + * - called from krxiod + */ +static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call) +{ + struct afs_async_op *op = call->app_user; + + _enter("{op=%p cst=%u}", op, call->app_call_state); + + switch (call->app_call_state) { + case RXRPC_CSTATE_COMPLETE: + afs_kafsasyncd_attend_op(op); + break; + case RXRPC_CSTATE_CLNT_RCV_REPLY: + if (call->app_async_read) + break; + case RXRPC_CSTATE_CLNT_GOT_REPLY: + if (call->app_read_count == 0) + break; + printk("kAFS: Reply bigger than expected" + " {cst=%u asyn=%d mark=%Zu rdy=%Zu pr=%u%s}", + call->app_call_state, + call->app_async_read, + call->app_mark, + call->app_ready_qty, + call->pkt_rcv_count, + call->app_last_rcv ? " last" : ""); + + rxrpc_call_abort(call, -EBADMSG); + break; + default: + BUG(); + } + + _leave(""); + +} /* end afs_rxvl_get_entry_by_id_attn() */ + +/*****************************************************************************/ +/* + * handle error events on an async get-entry-by-ID op + * - called from krxiod + */ +static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call) +{ + struct afs_async_op *op = call->app_user; + + _enter("{op=%p cst=%u}", op, call->app_call_state); + + afs_kafsasyncd_attend_op(op); + + _leave(""); + +} /* end afs_rxvl_get_entry_by_id_error() */ diff --git a/fs/afs/vlclient.h b/fs/afs/vlclient.h new file mode 100644 index 00000000000..e3d601179c4 --- /dev/null +++ b/fs/afs/vlclient.h @@ -0,0 +1,93 @@ +/* vlclient.h: Volume Location Service client interface + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_VLCLIENT_H +#define _LINUX_AFS_VLCLIENT_H + +#include "types.h" + +enum AFSVL_Errors { + AFSVL_IDEXIST = 363520, /* Volume Id entry exists in vl database */ + AFSVL_IO = 363521, /* I/O related error */ + AFSVL_NAMEEXIST = 363522, /* Volume name entry exists in vl database */ + AFSVL_CREATEFAIL = 363523, /* Internal creation failure */ + AFSVL_NOENT = 363524, /* No such entry */ + AFSVL_EMPTY = 363525, /* Vl database is empty */ + AFSVL_ENTDELETED = 363526, /* Entry is deleted (soft delete) */ + AFSVL_BADNAME = 363527, /* Volume name is illegal */ + AFSVL_BADINDEX = 363528, /* Index is out of range */ + AFSVL_BADVOLTYPE = 363529, /* Bad volume type */ + AFSVL_BADSERVER = 363530, /* Illegal server number (out of range) */ + AFSVL_BADPARTITION = 363531, /* Bad partition number */ + AFSVL_REPSFULL = 363532, /* Run out of space for Replication sites */ + AFSVL_NOREPSERVER = 363533, /* No such Replication server site exists */ + AFSVL_DUPREPSERVER = 363534, /* Replication site already exists */ + AFSVL_RWNOTFOUND = 363535, /* Parent R/W entry not found */ + AFSVL_BADREFCOUNT = 363536, /* Illegal Reference Count number */ + AFSVL_SIZEEXCEEDED = 363537, /* Vl size for attributes exceeded */ + AFSVL_BADENTRY = 363538, /* Bad incoming vl entry */ + AFSVL_BADVOLIDBUMP = 363539, /* Illegal max volid increment */ + AFSVL_IDALREADYHASHED = 363540, /* RO/BACK id already hashed */ + AFSVL_ENTRYLOCKED = 363541, /* Vl entry is already locked */ + AFSVL_BADVOLOPER = 363542, /* Bad volume operation code */ + AFSVL_BADRELLOCKTYPE = 363543, /* Bad release lock type */ + AFSVL_RERELEASE = 363544, /* Status report: last release was aborted */ + AFSVL_BADSERVERFLAG = 363545, /* Invalid replication site server °ag */ + AFSVL_PERM = 363546, /* No permission access */ + AFSVL_NOMEM = 363547, /* malloc/realloc failed to alloc enough memory */ +}; + +/* maps to "struct vldbentry" in vvl-spec.pdf */ +struct afs_vldbentry { + char name[65]; /* name of volume (including NUL char) */ + afs_voltype_t type; /* volume type */ + unsigned num_servers; /* num servers that hold instances of this vol */ + unsigned clone_id; /* cloning ID */ + + unsigned flags; +#define AFS_VLF_RWEXISTS 0x1000 /* R/W volume exists */ +#define AFS_VLF_ROEXISTS 0x2000 /* R/O volume exists */ +#define AFS_VLF_BACKEXISTS 0x4000 /* backup volume exists */ + + afs_volid_t volume_ids[3]; /* volume IDs */ + + struct { + struct in_addr addr; /* server address */ + unsigned partition; /* partition ID on this server */ + unsigned flags; /* server specific flags */ +#define AFS_VLSF_NEWREPSITE 0x0001 /* unused */ +#define AFS_VLSF_ROVOL 0x0002 /* this server holds a R/O instance of the volume */ +#define AFS_VLSF_RWVOL 0x0004 /* this server holds a R/W instance of the volume */ +#define AFS_VLSF_BACKVOL 0x0008 /* this server holds a backup instance of the volume */ + } servers[8]; + +}; + +/* look up a volume location database entry by name */ +extern int afs_rxvl_get_entry_by_name(struct afs_server *server, + const char *volname, + unsigned volnamesz, + struct afs_cache_vlocation *entry); + +/* look up a volume location database entry by ID */ +extern int afs_rxvl_get_entry_by_id(struct afs_server *server, + afs_volid_t volid, + afs_voltype_t voltype, + struct afs_cache_vlocation *entry); + +extern int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op, + afs_volid_t volid, + afs_voltype_t voltype); + +extern int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op, + struct afs_cache_vlocation *entry); + +#endif /* _LINUX_AFS_VLCLIENT_H */ diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c new file mode 100644 index 00000000000..eced20618ec --- /dev/null +++ b/fs/afs/vlocation.c @@ -0,0 +1,954 @@ +/* vlocation.c: volume location management + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include "volume.h" +#include "cell.h" +#include "cmservice.h" +#include "fsclient.h" +#include "vlclient.h" +#include "kafstimod.h" +#include <rxrpc/connection.h> +#include "internal.h" + +#define AFS_VLDB_TIMEOUT HZ*1000 + +static void afs_vlocation_update_timer(struct afs_timer *timer); +static void afs_vlocation_update_attend(struct afs_async_op *op); +static void afs_vlocation_update_discard(struct afs_async_op *op); +static void __afs_put_vlocation(struct afs_vlocation *vlocation); + +static void __afs_vlocation_timeout(struct afs_timer *timer) +{ + struct afs_vlocation *vlocation = + list_entry(timer, struct afs_vlocation, timeout); + + _debug("VL TIMEOUT [%s{u=%d}]", + vlocation->vldb.name, atomic_read(&vlocation->usage)); + + afs_vlocation_do_timeout(vlocation); +} + +static const struct afs_timer_ops afs_vlocation_timer_ops = { + .timed_out = __afs_vlocation_timeout, +}; + +static const struct afs_timer_ops afs_vlocation_update_timer_ops = { + .timed_out = afs_vlocation_update_timer, +}; + +static const struct afs_async_op_ops afs_vlocation_update_op_ops = { + .attend = afs_vlocation_update_attend, + .discard = afs_vlocation_update_discard, +}; + +static LIST_HEAD(afs_vlocation_update_pendq); /* queue of VLs awaiting update */ +static struct afs_vlocation *afs_vlocation_update; /* VL currently being updated */ +static DEFINE_SPINLOCK(afs_vlocation_update_lock); /* lock guarding update queue */ + +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_vlocation_cache_match(void *target, + const void *entry); +static void afs_vlocation_cache_update(void *source, void *entry); + +struct cachefs_index_def afs_vlocation_cache_index_def = { + .name = "vldb", + .data_size = sizeof(struct afs_cache_vlocation), + .keys[0] = { CACHEFS_INDEX_KEYS_ASCIIZ, 64 }, + .match = afs_vlocation_cache_match, + .update = afs_vlocation_cache_update, +}; +#endif + +/*****************************************************************************/ +/* + * iterate through the VL servers in a cell until one of them admits knowing + * about the volume in question + * - caller must have cell->vl_sem write-locked + */ +static int afs_vlocation_access_vl_by_name(struct afs_vlocation *vlocation, + const char *name, + unsigned namesz, + struct afs_cache_vlocation *vldb) +{ + struct afs_server *server = NULL; + struct afs_cell *cell = vlocation->cell; + int count, ret; + + _enter("%s,%*.*s,%u", cell->name, namesz, namesz, name, namesz); + + ret = -ENOMEDIUM; + for (count = cell->vl_naddrs; count > 0; count--) { + _debug("CellServ[%hu]: %08x", + cell->vl_curr_svix, + cell->vl_addrs[cell->vl_curr_svix].s_addr); + + /* try and create a server */ + ret = afs_server_lookup(cell, + &cell->vl_addrs[cell->vl_curr_svix], + &server); + switch (ret) { + case 0: + break; + case -ENOMEM: + case -ENONET: + goto out; + default: + goto rotate; + } + + /* attempt to access the VL server */ + ret = afs_rxvl_get_entry_by_name(server, name, namesz, vldb); + switch (ret) { + case 0: + afs_put_server(server); + goto out; + case -ENOMEM: + case -ENONET: + case -ENETUNREACH: + case -EHOSTUNREACH: + case -ECONNREFUSED: + down_write(&server->sem); + if (server->vlserver) { + rxrpc_put_connection(server->vlserver); + server->vlserver = NULL; + } + up_write(&server->sem); + afs_put_server(server); + if (ret == -ENOMEM || ret == -ENONET) + goto out; + goto rotate; + case -ENOMEDIUM: + afs_put_server(server); + goto out; + default: + afs_put_server(server); + ret = -ENOMEDIUM; + goto rotate; + } + + /* rotate the server records upon lookup failure */ + rotate: + cell->vl_curr_svix++; + cell->vl_curr_svix %= cell->vl_naddrs; + } + + out: + _leave(" = %d", ret); + return ret; + +} /* end afs_vlocation_access_vl_by_name() */ + +/*****************************************************************************/ +/* + * iterate through the VL servers in a cell until one of them admits knowing + * about the volume in question + * - caller must have cell->vl_sem write-locked + */ +static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vlocation, + afs_volid_t volid, + afs_voltype_t voltype, + struct afs_cache_vlocation *vldb) +{ + struct afs_server *server = NULL; + struct afs_cell *cell = vlocation->cell; + int count, ret; + + _enter("%s,%x,%d,", cell->name, volid, voltype); + + ret = -ENOMEDIUM; + for (count = cell->vl_naddrs; count > 0; count--) { + _debug("CellServ[%hu]: %08x", + cell->vl_curr_svix, + cell->vl_addrs[cell->vl_curr_svix].s_addr); + + /* try and create a server */ + ret = afs_server_lookup(cell, + &cell->vl_addrs[cell->vl_curr_svix], + &server); + switch (ret) { + case 0: + break; + case -ENOMEM: + case -ENONET: + goto out; + default: + goto rotate; + } + + /* attempt to access the VL server */ + ret = afs_rxvl_get_entry_by_id(server, volid, voltype, vldb); + switch (ret) { + case 0: + afs_put_server(server); + goto out; + case -ENOMEM: + case -ENONET: + case -ENETUNREACH: + case -EHOSTUNREACH: + case -ECONNREFUSED: + down_write(&server->sem); + if (server->vlserver) { + rxrpc_put_connection(server->vlserver); + server->vlserver = NULL; + } + up_write(&server->sem); + afs_put_server(server); + if (ret == -ENOMEM || ret == -ENONET) + goto out; + goto rotate; + case -ENOMEDIUM: + afs_put_server(server); + goto out; + default: + afs_put_server(server); + ret = -ENOMEDIUM; + goto rotate; + } + + /* rotate the server records upon lookup failure */ + rotate: + cell->vl_curr_svix++; + cell->vl_curr_svix %= cell->vl_naddrs; + } + + out: + _leave(" = %d", ret); + return ret; + +} /* end afs_vlocation_access_vl_by_id() */ + +/*****************************************************************************/ +/* + * lookup volume location + * - caller must have cell->vol_sem write-locked + * - iterate through the VL servers in a cell until one of them admits knowing + * about the volume in question + * - lookup in the local cache if not able to find on the VL server + * - insert/update in the local cache if did get a VL response + */ +int afs_vlocation_lookup(struct afs_cell *cell, + const char *name, + unsigned namesz, + struct afs_vlocation **_vlocation) +{ + struct afs_cache_vlocation vldb; + struct afs_vlocation *vlocation; + afs_voltype_t voltype; + afs_volid_t vid; + int active = 0, ret; + + _enter("{%s},%*.*s,%u,", cell->name, namesz, namesz, name, namesz); + + if (namesz > sizeof(vlocation->vldb.name)) { + _leave(" = -ENAMETOOLONG"); + return -ENAMETOOLONG; + } + + /* search the cell's active list first */ + list_for_each_entry(vlocation, &cell->vl_list, link) { + if (namesz < sizeof(vlocation->vldb.name) && + vlocation->vldb.name[namesz] != '\0') + continue; + + if (memcmp(vlocation->vldb.name, name, namesz) == 0) + goto found_in_memory; + } + + /* search the cell's graveyard list second */ + spin_lock(&cell->vl_gylock); + list_for_each_entry(vlocation, &cell->vl_graveyard, link) { + if (namesz < sizeof(vlocation->vldb.name) && + vlocation->vldb.name[namesz] != '\0') + continue; + + if (memcmp(vlocation->vldb.name, name, namesz) == 0) + goto found_in_graveyard; + } + spin_unlock(&cell->vl_gylock); + + /* not in the cell's in-memory lists - create a new record */ + vlocation = kmalloc(sizeof(struct afs_vlocation), GFP_KERNEL); + if (!vlocation) + return -ENOMEM; + + memset(vlocation, 0, sizeof(struct afs_vlocation)); + atomic_set(&vlocation->usage, 1); + INIT_LIST_HEAD(&vlocation->link); + rwlock_init(&vlocation->lock); + memcpy(vlocation->vldb.name, name, namesz); + + afs_timer_init(&vlocation->timeout, &afs_vlocation_timer_ops); + afs_timer_init(&vlocation->upd_timer, &afs_vlocation_update_timer_ops); + afs_async_op_init(&vlocation->upd_op, &afs_vlocation_update_op_ops); + + afs_get_cell(cell); + vlocation->cell = cell; + + list_add_tail(&vlocation->link, &cell->vl_list); + +#ifdef AFS_CACHING_SUPPORT + /* we want to store it in the cache, plus it might already be + * encached */ + cachefs_acquire_cookie(cell->cache, + &afs_volume_cache_index_def, + vlocation, + &vlocation->cache); + + if (vlocation->valid) + goto found_in_cache; +#endif + + /* try to look up an unknown volume in the cell VL databases by name */ + ret = afs_vlocation_access_vl_by_name(vlocation, name, namesz, &vldb); + if (ret < 0) { + printk("kAFS: failed to locate '%*.*s' in cell '%s'\n", + namesz, namesz, name, cell->name); + goto error; + } + + goto found_on_vlserver; + + found_in_graveyard: + /* found in the graveyard - resurrect */ + _debug("found in graveyard"); + atomic_inc(&vlocation->usage); + list_del(&vlocation->link); + list_add_tail(&vlocation->link, &cell->vl_list); + spin_unlock(&cell->vl_gylock); + + afs_kafstimod_del_timer(&vlocation->timeout); + goto active; + + found_in_memory: + /* found in memory - check to see if it's active */ + _debug("found in memory"); + atomic_inc(&vlocation->usage); + + active: + active = 1; + +#ifdef AFS_CACHING_SUPPORT + found_in_cache: +#endif + /* try to look up a cached volume in the cell VL databases by ID */ + _debug("found in cache"); + + _debug("Locally Cached: %s %02x { %08x(%x) %08x(%x) %08x(%x) }", + vlocation->vldb.name, + vlocation->vldb.vidmask, + ntohl(vlocation->vldb.servers[0].s_addr), + vlocation->vldb.srvtmask[0], + ntohl(vlocation->vldb.servers[1].s_addr), + vlocation->vldb.srvtmask[1], + ntohl(vlocation->vldb.servers[2].s_addr), + vlocation->vldb.srvtmask[2] + ); + + _debug("Vids: %08x %08x %08x", + vlocation->vldb.vid[0], + vlocation->vldb.vid[1], + vlocation->vldb.vid[2]); + + if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) { + vid = vlocation->vldb.vid[0]; + voltype = AFSVL_RWVOL; + } + else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) { + vid = vlocation->vldb.vid[1]; + voltype = AFSVL_ROVOL; + } + else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) { + vid = vlocation->vldb.vid[2]; + voltype = AFSVL_BACKVOL; + } + else { + BUG(); + vid = 0; + voltype = 0; + } + + ret = afs_vlocation_access_vl_by_id(vlocation, vid, voltype, &vldb); + switch (ret) { + /* net error */ + default: + printk("kAFS: failed to volume '%*.*s' (%x) up in '%s': %d\n", + namesz, namesz, name, vid, cell->name, ret); + goto error; + + /* pulled from local cache into memory */ + case 0: + goto found_on_vlserver; + + /* uh oh... looks like the volume got deleted */ + case -ENOMEDIUM: + printk("kAFS: volume '%*.*s' (%x) does not exist '%s'\n", + namesz, namesz, name, vid, cell->name); + + /* TODO: make existing record unavailable */ + goto error; + } + + found_on_vlserver: + _debug("Done VL Lookup: %*.*s %02x { %08x(%x) %08x(%x) %08x(%x) }", + namesz, namesz, name, + vldb.vidmask, + ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0], + ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1], + ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2] + ); + + _debug("Vids: %08x %08x %08x", vldb.vid[0], vldb.vid[1], vldb.vid[2]); + + if ((namesz < sizeof(vlocation->vldb.name) && + vlocation->vldb.name[namesz] != '\0') || + memcmp(vldb.name, name, namesz) != 0) + printk("kAFS: name of volume '%*.*s' changed to '%s' on server\n", + namesz, namesz, name, vldb.name); + + memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb)); + + afs_kafstimod_add_timer(&vlocation->upd_timer, 10 * HZ); + +#ifdef AFS_CACHING_SUPPORT + /* update volume entry in local cache */ + cachefs_update_cookie(vlocation->cache); +#endif + + *_vlocation = vlocation; + _leave(" = 0 (%p)",vlocation); + return 0; + + error: + if (vlocation) { + if (active) { + __afs_put_vlocation(vlocation); + } + else { + list_del(&vlocation->link); +#ifdef AFS_CACHING_SUPPORT + cachefs_relinquish_cookie(vlocation->cache, 0); +#endif + afs_put_cell(vlocation->cell); + kfree(vlocation); + } + } + + _leave(" = %d", ret); + return ret; +} /* end afs_vlocation_lookup() */ + +/*****************************************************************************/ +/* + * finish using a volume location record + * - caller must have cell->vol_sem write-locked + */ +static void __afs_put_vlocation(struct afs_vlocation *vlocation) +{ + struct afs_cell *cell; + + if (!vlocation) + return; + + _enter("%s", vlocation->vldb.name); + + cell = vlocation->cell; + + /* sanity check */ + BUG_ON(atomic_read(&vlocation->usage) <= 0); + + spin_lock(&cell->vl_gylock); + if (likely(!atomic_dec_and_test(&vlocation->usage))) { + spin_unlock(&cell->vl_gylock); + _leave(""); + return; + } + + /* move to graveyard queue */ + list_del(&vlocation->link); + list_add_tail(&vlocation->link,&cell->vl_graveyard); + + /* remove from pending timeout queue (refcounted if actually being + * updated) */ + list_del_init(&vlocation->upd_op.link); + + /* time out in 10 secs */ + afs_kafstimod_del_timer(&vlocation->upd_timer); + afs_kafstimod_add_timer(&vlocation->timeout, 10 * HZ); + + spin_unlock(&cell->vl_gylock); + + _leave(" [killed]"); +} /* end __afs_put_vlocation() */ + +/*****************************************************************************/ +/* + * finish using a volume location record + */ +void afs_put_vlocation(struct afs_vlocation *vlocation) +{ + if (vlocation) { + struct afs_cell *cell = vlocation->cell; + + down_write(&cell->vl_sem); + __afs_put_vlocation(vlocation); + up_write(&cell->vl_sem); + } +} /* end afs_put_vlocation() */ + +/*****************************************************************************/ +/* + * timeout vlocation record + * - removes from the cell's graveyard if the usage count is zero + */ +void afs_vlocation_do_timeout(struct afs_vlocation *vlocation) +{ + struct afs_cell *cell; + + _enter("%s", vlocation->vldb.name); + + cell = vlocation->cell; + + BUG_ON(atomic_read(&vlocation->usage) < 0); + + /* remove from graveyard if still dead */ + spin_lock(&cell->vl_gylock); + if (atomic_read(&vlocation->usage) == 0) + list_del_init(&vlocation->link); + else + vlocation = NULL; + spin_unlock(&cell->vl_gylock); + + if (!vlocation) { + _leave(""); + return; /* resurrected */ + } + + /* we can now destroy it properly */ +#ifdef AFS_CACHING_SUPPORT + cachefs_relinquish_cookie(vlocation->cache, 0); +#endif + afs_put_cell(cell); + + kfree(vlocation); + + _leave(" [destroyed]"); +} /* end afs_vlocation_do_timeout() */ + +/*****************************************************************************/ +/* + * send an update operation to the currently selected server + */ +static int afs_vlocation_update_begin(struct afs_vlocation *vlocation) +{ + afs_voltype_t voltype; + afs_volid_t vid; + int ret; + + _enter("%s{ufs=%u ucs=%u}", + vlocation->vldb.name, + vlocation->upd_first_svix, + vlocation->upd_curr_svix); + + /* try to look up a cached volume in the cell VL databases by ID */ + if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) { + vid = vlocation->vldb.vid[0]; + voltype = AFSVL_RWVOL; + } + else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) { + vid = vlocation->vldb.vid[1]; + voltype = AFSVL_ROVOL; + } + else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) { + vid = vlocation->vldb.vid[2]; + voltype = AFSVL_BACKVOL; + } + else { + BUG(); + vid = 0; + voltype = 0; + } + + /* contact the chosen server */ + ret = afs_server_lookup( + vlocation->cell, + &vlocation->cell->vl_addrs[vlocation->upd_curr_svix], + &vlocation->upd_op.server); + + switch (ret) { + case 0: + break; + case -ENOMEM: + case -ENONET: + default: + _leave(" = %d", ret); + return ret; + } + + /* initiate the update operation */ + ret = afs_rxvl_get_entry_by_id_async(&vlocation->upd_op, vid, voltype); + if (ret < 0) { + _leave(" = %d", ret); + return ret; + } + + _leave(" = %d", ret); + return ret; +} /* end afs_vlocation_update_begin() */ + +/*****************************************************************************/ +/* + * abandon updating a VL record + * - does not restart the update timer + */ +static void afs_vlocation_update_abandon(struct afs_vlocation *vlocation, + afs_vlocation_upd_t state, + int ret) +{ + _enter("%s,%u", vlocation->vldb.name, state); + + if (ret < 0) + printk("kAFS: Abandoning VL update '%s': %d\n", + vlocation->vldb.name, ret); + + /* discard the server record */ + afs_put_server(vlocation->upd_op.server); + vlocation->upd_op.server = NULL; + + spin_lock(&afs_vlocation_update_lock); + afs_vlocation_update = NULL; + vlocation->upd_state = state; + + /* TODO: start updating next VL record on pending list */ + + spin_unlock(&afs_vlocation_update_lock); + + _leave(""); +} /* end afs_vlocation_update_abandon() */ + +/*****************************************************************************/ +/* + * handle periodic update timeouts and busy retry timeouts + * - called from kafstimod + */ +static void afs_vlocation_update_timer(struct afs_timer *timer) +{ + struct afs_vlocation *vlocation = + list_entry(timer, struct afs_vlocation, upd_timer); + int ret; + + _enter("%s", vlocation->vldb.name); + + /* only update if not in the graveyard (defend against putting too) */ + spin_lock(&vlocation->cell->vl_gylock); + + if (!atomic_read(&vlocation->usage)) + goto out_unlock1; + + spin_lock(&afs_vlocation_update_lock); + + /* if we were woken up due to EBUSY sleep then restart immediately if + * possible or else jump to front of pending queue */ + if (vlocation->upd_state == AFS_VLUPD_BUSYSLEEP) { + if (afs_vlocation_update) { + list_add(&vlocation->upd_op.link, + &afs_vlocation_update_pendq); + } + else { + afs_get_vlocation(vlocation); + afs_vlocation_update = vlocation; + vlocation->upd_state = AFS_VLUPD_INPROGRESS; + } + goto out_unlock2; + } + + /* put on pending queue if there's already another update in progress */ + if (afs_vlocation_update) { + vlocation->upd_state = AFS_VLUPD_PENDING; + list_add_tail(&vlocation->upd_op.link, + &afs_vlocation_update_pendq); + goto out_unlock2; + } + + /* hold a ref on it while actually updating */ + afs_get_vlocation(vlocation); + afs_vlocation_update = vlocation; + vlocation->upd_state = AFS_VLUPD_INPROGRESS; + + spin_unlock(&afs_vlocation_update_lock); + spin_unlock(&vlocation->cell->vl_gylock); + + /* okay... we can start the update */ + _debug("BEGIN VL UPDATE [%s]", vlocation->vldb.name); + vlocation->upd_first_svix = vlocation->cell->vl_curr_svix; + vlocation->upd_curr_svix = vlocation->upd_first_svix; + vlocation->upd_rej_cnt = 0; + vlocation->upd_busy_cnt = 0; + + ret = afs_vlocation_update_begin(vlocation); + if (ret < 0) { + afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret); + afs_kafstimod_add_timer(&vlocation->upd_timer, + AFS_VLDB_TIMEOUT); + afs_put_vlocation(vlocation); + } + + _leave(""); + return; + + out_unlock2: + spin_unlock(&afs_vlocation_update_lock); + out_unlock1: + spin_unlock(&vlocation->cell->vl_gylock); + _leave(""); + return; + +} /* end afs_vlocation_update_timer() */ + +/*****************************************************************************/ +/* + * attend to an update operation upon which an event happened + * - called in kafsasyncd context + */ +static void afs_vlocation_update_attend(struct afs_async_op *op) +{ + struct afs_cache_vlocation vldb; + struct afs_vlocation *vlocation = + list_entry(op, struct afs_vlocation, upd_op); + unsigned tmp; + int ret; + + _enter("%s", vlocation->vldb.name); + + ret = afs_rxvl_get_entry_by_id_async2(op, &vldb); + switch (ret) { + case -EAGAIN: + _leave(" [unfinished]"); + return; + + case 0: + _debug("END VL UPDATE: %d\n", ret); + vlocation->valid = 1; + + _debug("Done VL Lookup: %02x { %08x(%x) %08x(%x) %08x(%x) }", + vldb.vidmask, + ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0], + ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1], + ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2] + ); + + _debug("Vids: %08x %08x %08x", + vldb.vid[0], vldb.vid[1], vldb.vid[2]); + + afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0); + + down_write(&vlocation->cell->vl_sem); + + /* actually update the cache */ + if (strncmp(vldb.name, vlocation->vldb.name, + sizeof(vlocation->vldb.name)) != 0) + printk("kAFS: name of volume '%s'" + " changed to '%s' on server\n", + vlocation->vldb.name, vldb.name); + + memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb)); + +#if 0 + /* TODO update volume entry in local cache */ +#endif + + up_write(&vlocation->cell->vl_sem); + + if (ret < 0) + printk("kAFS: failed to update local cache: %d\n", ret); + + afs_kafstimod_add_timer(&vlocation->upd_timer, + AFS_VLDB_TIMEOUT); + afs_put_vlocation(vlocation); + _leave(" [found]"); + return; + + case -ENOMEDIUM: + vlocation->upd_rej_cnt++; + goto try_next; + + /* the server is locked - retry in a very short while */ + case -EBUSY: + vlocation->upd_busy_cnt++; + if (vlocation->upd_busy_cnt > 3) + goto try_next; /* too many retries */ + + afs_vlocation_update_abandon(vlocation, + AFS_VLUPD_BUSYSLEEP, 0); + afs_kafstimod_add_timer(&vlocation->upd_timer, HZ / 2); + afs_put_vlocation(vlocation); + _leave(" [busy]"); + return; + + case -ENETUNREACH: + case -EHOSTUNREACH: + case -ECONNREFUSED: + case -EREMOTEIO: + /* record bad vlserver info in the cell too + * - TODO: use down_write_trylock() if available + */ + if (vlocation->upd_curr_svix == vlocation->cell->vl_curr_svix) + vlocation->cell->vl_curr_svix = + vlocation->cell->vl_curr_svix % + vlocation->cell->vl_naddrs; + + case -EBADRQC: + case -EINVAL: + case -EACCES: + case -EBADMSG: + goto try_next; + + default: + goto abandon; + } + + /* try contacting the next server */ + try_next: + vlocation->upd_busy_cnt = 0; + + /* discard the server record */ + afs_put_server(vlocation->upd_op.server); + vlocation->upd_op.server = NULL; + + tmp = vlocation->cell->vl_naddrs; + if (tmp == 0) + goto abandon; + + vlocation->upd_curr_svix++; + if (vlocation->upd_curr_svix >= tmp) + vlocation->upd_curr_svix = 0; + if (vlocation->upd_first_svix >= tmp) + vlocation->upd_first_svix = tmp - 1; + + /* move to the next server */ + if (vlocation->upd_curr_svix != vlocation->upd_first_svix) { + afs_vlocation_update_begin(vlocation); + _leave(" [next]"); + return; + } + + /* run out of servers to try - was the volume rejected? */ + if (vlocation->upd_rej_cnt > 0) { + printk("kAFS: Active volume no longer valid '%s'\n", + vlocation->vldb.name); + vlocation->valid = 0; + afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0); + afs_kafstimod_add_timer(&vlocation->upd_timer, + AFS_VLDB_TIMEOUT); + afs_put_vlocation(vlocation); + _leave(" [invalidated]"); + return; + } + + /* abandon the update */ + abandon: + afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret); + afs_kafstimod_add_timer(&vlocation->upd_timer, HZ * 10); + afs_put_vlocation(vlocation); + _leave(" [abandoned]"); + +} /* end afs_vlocation_update_attend() */ + +/*****************************************************************************/ +/* + * deal with an update operation being discarded + * - called in kafsasyncd context when it's dying due to rmmod + * - the call has already been aborted and put()'d + */ +static void afs_vlocation_update_discard(struct afs_async_op *op) +{ + struct afs_vlocation *vlocation = + list_entry(op, struct afs_vlocation, upd_op); + + _enter("%s", vlocation->vldb.name); + + afs_put_server(op->server); + op->server = NULL; + + afs_put_vlocation(vlocation); + + _leave(""); +} /* end afs_vlocation_update_discard() */ + +/*****************************************************************************/ +/* + * match a VLDB record stored in the cache + * - may also load target from entry + */ +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_vlocation_cache_match(void *target, + const void *entry) +{ + const struct afs_cache_vlocation *vldb = entry; + struct afs_vlocation *vlocation = target; + + _enter("{%s},{%s}", vlocation->vldb.name, vldb->name); + + if (strncmp(vlocation->vldb.name, vldb->name, sizeof(vldb->name)) == 0 + ) { + if (!vlocation->valid || + vlocation->vldb.rtime == vldb->rtime + ) { + vlocation->vldb = *vldb; + vlocation->valid = 1; + _leave(" = SUCCESS [c->m]"); + return CACHEFS_MATCH_SUCCESS; + } + /* need to update cache if cached info differs */ + else if (memcmp(&vlocation->vldb, vldb, sizeof(*vldb)) != 0) { + /* delete if VIDs for this name differ */ + if (memcmp(&vlocation->vldb.vid, + &vldb->vid, + sizeof(vldb->vid)) != 0) { + _leave(" = DELETE"); + return CACHEFS_MATCH_SUCCESS_DELETE; + } + + _leave(" = UPDATE"); + return CACHEFS_MATCH_SUCCESS_UPDATE; + } + else { + _leave(" = SUCCESS"); + return CACHEFS_MATCH_SUCCESS; + } + } + + _leave(" = FAILED"); + return CACHEFS_MATCH_FAILED; +} /* end afs_vlocation_cache_match() */ +#endif + +/*****************************************************************************/ +/* + * update a VLDB record stored in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_vlocation_cache_update(void *source, void *entry) +{ + struct afs_cache_vlocation *vldb = entry; + struct afs_vlocation *vlocation = source; + + _enter(""); + + *vldb = vlocation->vldb; + +} /* end afs_vlocation_cache_update() */ +#endif diff --git a/fs/afs/vnode.c b/fs/afs/vnode.c new file mode 100644 index 00000000000..9867fef3261 --- /dev/null +++ b/fs/afs/vnode.c @@ -0,0 +1,395 @@ +/* vnode.c: AFS vnode management + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include "volume.h" +#include "cell.h" +#include "cmservice.h" +#include "fsclient.h" +#include "vlclient.h" +#include "vnode.h" +#include "internal.h" + +static void afs_vnode_cb_timed_out(struct afs_timer *timer); + +struct afs_timer_ops afs_vnode_cb_timed_out_ops = { + .timed_out = afs_vnode_cb_timed_out, +}; + +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_vnode_cache_match(void *target, + const void *entry); +static void afs_vnode_cache_update(void *source, void *entry); + +struct cachefs_index_def afs_vnode_cache_index_def = { + .name = "vnode", + .data_size = sizeof(struct afs_cache_vnode), + .keys[0] = { CACHEFS_INDEX_KEYS_BIN, 4 }, + .match = afs_vnode_cache_match, + .update = afs_vnode_cache_update, +}; +#endif + +/*****************************************************************************/ +/* + * handle a callback timing out + * TODO: retain a ref to vnode struct for an outstanding callback timeout + */ +static void afs_vnode_cb_timed_out(struct afs_timer *timer) +{ + struct afs_server *oldserver; + struct afs_vnode *vnode; + + vnode = list_entry(timer, struct afs_vnode, cb_timeout); + + _enter("%p", vnode); + + /* set the changed flag in the vnode and release the server */ + spin_lock(&vnode->lock); + + oldserver = xchg(&vnode->cb_server, NULL); + if (oldserver) { + vnode->flags |= AFS_VNODE_CHANGED; + + spin_lock(&afs_cb_hash_lock); + list_del_init(&vnode->cb_hash_link); + spin_unlock(&afs_cb_hash_lock); + + spin_lock(&oldserver->cb_lock); + list_del_init(&vnode->cb_link); + spin_unlock(&oldserver->cb_lock); + } + + spin_unlock(&vnode->lock); + + afs_put_server(oldserver); + + _leave(""); +} /* end afs_vnode_cb_timed_out() */ + +/*****************************************************************************/ +/* + * finish off updating the recorded status of a file + * - starts callback expiry timer + * - adds to server's callback list + */ +static void afs_vnode_finalise_status_update(struct afs_vnode *vnode, + struct afs_server *server, + int ret) +{ + struct afs_server *oldserver = NULL; + + _enter("%p,%p,%d", vnode, server, ret); + + spin_lock(&vnode->lock); + + vnode->flags &= ~AFS_VNODE_CHANGED; + + if (ret == 0) { + /* adjust the callback timeout appropriately */ + afs_kafstimod_add_timer(&vnode->cb_timeout, + vnode->cb_expiry * HZ); + + spin_lock(&afs_cb_hash_lock); + list_del(&vnode->cb_hash_link); + list_add_tail(&vnode->cb_hash_link, + &afs_cb_hash(server, &vnode->fid)); + spin_unlock(&afs_cb_hash_lock); + + /* swap ref to old callback server with that for new callback + * server */ + oldserver = xchg(&vnode->cb_server, server); + if (oldserver != server) { + if (oldserver) { + spin_lock(&oldserver->cb_lock); + list_del_init(&vnode->cb_link); + spin_unlock(&oldserver->cb_lock); + } + + afs_get_server(server); + spin_lock(&server->cb_lock); + list_add_tail(&vnode->cb_link, &server->cb_promises); + spin_unlock(&server->cb_lock); + } + else { + /* same server */ + oldserver = NULL; + } + } + else if (ret == -ENOENT) { + /* the file was deleted - clear the callback timeout */ + oldserver = xchg(&vnode->cb_server, NULL); + afs_kafstimod_del_timer(&vnode->cb_timeout); + + _debug("got NOENT from server - marking file deleted"); + vnode->flags |= AFS_VNODE_DELETED; + } + + vnode->update_cnt--; + + spin_unlock(&vnode->lock); + + wake_up_all(&vnode->update_waitq); + + afs_put_server(oldserver); + + _leave(""); + +} /* end afs_vnode_finalise_status_update() */ + +/*****************************************************************************/ +/* + * fetch file status from the volume + * - don't issue a fetch if: + * - the changed bit is not set and there's a valid callback + * - there are any outstanding ops that will fetch the status + * - TODO implement local caching + */ +int afs_vnode_fetch_status(struct afs_vnode *vnode) +{ + struct afs_server *server; + int ret; + + DECLARE_WAITQUEUE(myself, current); + + _enter("%s,{%u,%u,%u}", + vnode->volume->vlocation->vldb.name, + vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique); + + if (!(vnode->flags & AFS_VNODE_CHANGED) && vnode->cb_server) { + _leave(" [unchanged]"); + return 0; + } + + if (vnode->flags & AFS_VNODE_DELETED) { + _leave(" [deleted]"); + return -ENOENT; + } + + spin_lock(&vnode->lock); + + if (!(vnode->flags & AFS_VNODE_CHANGED)) { + spin_unlock(&vnode->lock); + _leave(" [unchanged]"); + return 0; + } + + if (vnode->update_cnt > 0) { + /* someone else started a fetch */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&vnode->update_waitq, &myself); + + /* wait for the status to be updated */ + for (;;) { + if (!(vnode->flags & AFS_VNODE_CHANGED)) + break; + if (vnode->flags & AFS_VNODE_DELETED) + break; + + /* it got updated and invalidated all before we saw + * it */ + if (vnode->update_cnt == 0) { + remove_wait_queue(&vnode->update_waitq, + &myself); + set_current_state(TASK_RUNNING); + goto get_anyway; + } + + spin_unlock(&vnode->lock); + + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + + spin_lock(&vnode->lock); + } + + remove_wait_queue(&vnode->update_waitq, &myself); + spin_unlock(&vnode->lock); + set_current_state(TASK_RUNNING); + + return vnode->flags & AFS_VNODE_DELETED ? -ENOENT : 0; + } + + get_anyway: + /* okay... we're going to have to initiate the op */ + vnode->update_cnt++; + + spin_unlock(&vnode->lock); + + /* merge AFS status fetches and clear outstanding callback on this + * vnode */ + do { + /* pick a server to query */ + ret = afs_volume_pick_fileserver(vnode->volume, &server); + if (ret<0) + return ret; + + _debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr)); + + ret = afs_rxfs_fetch_file_status(server, vnode, NULL); + + } while (!afs_volume_release_fileserver(vnode->volume, server, ret)); + + /* adjust the flags */ + afs_vnode_finalise_status_update(vnode, server, ret); + + _leave(" = %d", ret); + return ret; +} /* end afs_vnode_fetch_status() */ + +/*****************************************************************************/ +/* + * fetch file data from the volume + * - TODO implement caching and server failover + */ +int afs_vnode_fetch_data(struct afs_vnode *vnode, + struct afs_rxfs_fetch_descriptor *desc) +{ + struct afs_server *server; + int ret; + + _enter("%s,{%u,%u,%u}", + vnode->volume->vlocation->vldb.name, + vnode->fid.vid, + vnode->fid.vnode, + vnode->fid.unique); + + /* this op will fetch the status */ + spin_lock(&vnode->lock); + vnode->update_cnt++; + spin_unlock(&vnode->lock); + + /* merge in AFS status fetches and clear outstanding callback on this + * vnode */ + do { + /* pick a server to query */ + ret = afs_volume_pick_fileserver(vnode->volume, &server); + if (ret < 0) + return ret; + + _debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr)); + + ret = afs_rxfs_fetch_file_data(server, vnode, desc, NULL); + + } while (!afs_volume_release_fileserver(vnode->volume, server, ret)); + + /* adjust the flags */ + afs_vnode_finalise_status_update(vnode, server, ret); + + _leave(" = %d", ret); + return ret; + +} /* end afs_vnode_fetch_data() */ + +/*****************************************************************************/ +/* + * break any outstanding callback on a vnode + * - only relevent to server that issued it + */ +int afs_vnode_give_up_callback(struct afs_vnode *vnode) +{ + struct afs_server *server; + int ret; + + _enter("%s,{%u,%u,%u}", + vnode->volume->vlocation->vldb.name, + vnode->fid.vid, + vnode->fid.vnode, + vnode->fid.unique); + + spin_lock(&afs_cb_hash_lock); + list_del_init(&vnode->cb_hash_link); + spin_unlock(&afs_cb_hash_lock); + + /* set the changed flag in the vnode and release the server */ + spin_lock(&vnode->lock); + + afs_kafstimod_del_timer(&vnode->cb_timeout); + + server = xchg(&vnode->cb_server, NULL); + if (server) { + vnode->flags |= AFS_VNODE_CHANGED; + + spin_lock(&server->cb_lock); + list_del_init(&vnode->cb_link); + spin_unlock(&server->cb_lock); + } + + spin_unlock(&vnode->lock); + + ret = 0; + if (server) { + ret = afs_rxfs_give_up_callback(server, vnode); + afs_put_server(server); + } + + _leave(" = %d", ret); + return ret; +} /* end afs_vnode_give_up_callback() */ + +/*****************************************************************************/ +/* + * match a vnode record stored in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_vnode_cache_match(void *target, + const void *entry) +{ + const struct afs_cache_vnode *cvnode = entry; + struct afs_vnode *vnode = target; + + _enter("{%x,%x,%Lx},{%x,%x,%Lx}", + vnode->fid.vnode, + vnode->fid.unique, + vnode->status.version, + cvnode->vnode_id, + cvnode->vnode_unique, + cvnode->data_version); + + if (vnode->fid.vnode != cvnode->vnode_id) { + _leave(" = FAILED"); + return CACHEFS_MATCH_FAILED; + } + + if (vnode->fid.unique != cvnode->vnode_unique || + vnode->status.version != cvnode->data_version) { + _leave(" = DELETE"); + return CACHEFS_MATCH_SUCCESS_DELETE; + } + + _leave(" = SUCCESS"); + return CACHEFS_MATCH_SUCCESS; +} /* end afs_vnode_cache_match() */ +#endif + +/*****************************************************************************/ +/* + * update a vnode record stored in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_vnode_cache_update(void *source, void *entry) +{ + struct afs_cache_vnode *cvnode = entry; + struct afs_vnode *vnode = source; + + _enter(""); + + cvnode->vnode_id = vnode->fid.vnode; + cvnode->vnode_unique = vnode->fid.unique; + cvnode->data_version = vnode->status.version; + +} /* end afs_vnode_cache_update() */ +#endif diff --git a/fs/afs/vnode.h b/fs/afs/vnode.h new file mode 100644 index 00000000000..b86a97102e8 --- /dev/null +++ b/fs/afs/vnode.h @@ -0,0 +1,94 @@ +/* vnode.h: AFS vnode record + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_VNODE_H +#define _LINUX_AFS_VNODE_H + +#include <linux/fs.h> +#include "server.h" +#include "kafstimod.h" +#include "cache.h" + +#ifdef __KERNEL__ + +struct afs_rxfs_fetch_descriptor; + +/*****************************************************************************/ +/* + * vnode catalogue entry + */ +struct afs_cache_vnode +{ + afs_vnodeid_t vnode_id; /* vnode ID */ + unsigned vnode_unique; /* vnode ID uniquifier */ + afs_dataversion_t data_version; /* data version */ +}; + +#ifdef AFS_CACHING_SUPPORT +extern struct cachefs_index_def afs_vnode_cache_index_def; +#endif + +/*****************************************************************************/ +/* + * AFS inode private data + */ +struct afs_vnode +{ + struct inode vfs_inode; /* the VFS's inode record */ + + struct afs_volume *volume; /* volume on which vnode resides */ + struct afs_fid fid; /* the file identifier for this inode */ + struct afs_file_status status; /* AFS status info for this file */ +#ifdef AFS_CACHING_SUPPORT + struct cachefs_cookie *cache; /* caching cookie */ +#endif + + wait_queue_head_t update_waitq; /* status fetch waitqueue */ + unsigned update_cnt; /* number of outstanding ops that will update the + * status */ + spinlock_t lock; /* waitqueue/flags lock */ + unsigned flags; +#define AFS_VNODE_CHANGED 0x00000001 /* set if vnode reported changed by callback */ +#define AFS_VNODE_DELETED 0x00000002 /* set if vnode deleted on server */ +#define AFS_VNODE_MOUNTPOINT 0x00000004 /* set if vnode is a mountpoint symlink */ + + /* outstanding callback notification on this file */ + struct afs_server *cb_server; /* server that made the current promise */ + struct list_head cb_link; /* link in server's promises list */ + struct list_head cb_hash_link; /* link in master callback hash */ + struct afs_timer cb_timeout; /* timeout on promise */ + unsigned cb_version; /* callback version */ + unsigned cb_expiry; /* callback expiry time */ + afs_callback_type_t cb_type; /* type of callback */ +}; + +static inline struct afs_vnode *AFS_FS_I(struct inode *inode) +{ + return container_of(inode,struct afs_vnode,vfs_inode); +} + +static inline struct inode *AFS_VNODE_TO_I(struct afs_vnode *vnode) +{ + return &vnode->vfs_inode; +} + +extern int afs_vnode_fetch_status(struct afs_vnode *vnode); + +extern int afs_vnode_fetch_data(struct afs_vnode *vnode, + struct afs_rxfs_fetch_descriptor *desc); + +extern int afs_vnode_give_up_callback(struct afs_vnode *vnode); + +extern struct afs_timer_ops afs_vnode_cb_timed_out_ops; + +#endif /* __KERNEL__ */ + +#endif /* _LINUX_AFS_VNODE_H */ diff --git a/fs/afs/volume.c b/fs/afs/volume.c new file mode 100644 index 00000000000..0ff4b86476e --- /dev/null +++ b/fs/afs/volume.c @@ -0,0 +1,520 @@ +/* volume.c: AFS volume management + * + * Copyright (C) 2002 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/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include "volume.h" +#include "vnode.h" +#include "cell.h" +#include "cache.h" +#include "cmservice.h" +#include "fsclient.h" +#include "vlclient.h" +#include "internal.h" + +#ifdef __KDEBUG +static const char *afs_voltypes[] = { "R/W", "R/O", "BAK" }; +#endif + +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_volume_cache_match(void *target, + const void *entry); +static void afs_volume_cache_update(void *source, void *entry); + +struct cachefs_index_def afs_volume_cache_index_def = { + .name = "volume", + .data_size = sizeof(struct afs_cache_vhash), + .keys[0] = { CACHEFS_INDEX_KEYS_BIN, 1 }, + .keys[1] = { CACHEFS_INDEX_KEYS_BIN, 1 }, + .match = afs_volume_cache_match, + .update = afs_volume_cache_update, +}; +#endif + +/*****************************************************************************/ +/* + * lookup a volume by name + * - this 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 + */ +int afs_volume_lookup(const char *name, struct afs_cell *cell, int rwpath, + struct afs_volume **_volume) +{ + struct afs_vlocation *vlocation = NULL; + struct afs_volume *volume = NULL; + afs_voltype_t type; + const char *cellname, *volname, *suffix; + char srvtmask; + int force, ret, loop, cellnamesz, volnamesz; + + _enter("%s,,%d,", name, rwpath); + + if (!name || (name[0] != '%' && name[0] != '#') || !name[1]) { + printk("kAFS: unparsable volume name\n"); + return -EINVAL; + } + + /* determine the type of volume we're looking for */ + force = 0; + type = AFSVL_ROVOL; + + if (rwpath || name[0] == '%') { + type = AFSVL_RWVOL; + force = 1; + } + + suffix = strrchr(name, '.'); + if (suffix) { + if (strcmp(suffix, ".readonly") == 0) { + type = AFSVL_ROVOL; + force = 1; + } + else if (strcmp(suffix, ".backup") == 0) { + type = AFSVL_BACKVOL; + force = 1; + } + else if (suffix[1] == 0) { + } + else { + suffix = NULL; + } + } + + /* split the cell and volume names */ + name++; + volname = strchr(name, ':'); + if (volname) { + cellname = name; + cellnamesz = volname - name; + volname++; + } + else { + volname = name; + cellname = NULL; + cellnamesz = 0; + } + + volnamesz = suffix ? suffix - volname : strlen(volname); + + _debug("CELL:%*.*s [%p] VOLUME:%*.*s SUFFIX:%s TYPE:%d%s", + cellnamesz, cellnamesz, cellname ?: "", cell, + volnamesz, volnamesz, volname, suffix ?: "-", + type, + force ? " FORCE" : ""); + + /* lookup the cell record */ + if (cellname || !cell) { + ret = afs_cell_lookup(cellname, cellnamesz, &cell); + if (ret<0) { + printk("kAFS: unable to lookup cell '%s'\n", + cellname ?: ""); + goto error; + } + } + else { + afs_get_cell(cell); + } + + /* lookup the volume location record */ + ret = afs_vlocation_lookup(cell, volname, volnamesz, &vlocation); + if (ret < 0) + goto error; + + /* make the final decision on the type we want */ + ret = -ENOMEDIUM; + if (force && !(vlocation->vldb.vidmask & (1 << type))) + goto error; + + srvtmask = 0; + for (loop = 0; loop < vlocation->vldb.nservers; loop++) + srvtmask |= vlocation->vldb.srvtmask[loop]; + + if (force) { + if (!(srvtmask & (1 << type))) + goto error; + } + else if (srvtmask & AFS_VOL_VTM_RO) { + type = AFSVL_ROVOL; + } + else if (srvtmask & AFS_VOL_VTM_RW) { + type = AFSVL_RWVOL; + } + else { + goto error; + } + + down_write(&cell->vl_sem); + + /* is the volume already active? */ + if (vlocation->vols[type]) { + /* yes - re-use it */ + volume = vlocation->vols[type]; + afs_get_volume(volume); + goto success; + } + + /* create a new volume record */ + _debug("creating new volume record"); + + ret = -ENOMEM; + volume = kmalloc(sizeof(struct afs_volume), GFP_KERNEL); + if (!volume) + goto error_up; + + memset(volume, 0, sizeof(struct afs_volume)); + atomic_set(&volume->usage, 1); + volume->type = type; + volume->type_force = force; + volume->cell = cell; + volume->vid = vlocation->vldb.vid[type]; + + init_rwsem(&volume->server_sem); + + /* look up all the applicable server records */ + for (loop = 0; loop < 8; loop++) { + if (vlocation->vldb.srvtmask[loop] & (1 << volume->type)) { + ret = afs_server_lookup( + volume->cell, + &vlocation->vldb.servers[loop], + &volume->servers[volume->nservers]); + if (ret < 0) + goto error_discard; + + volume->nservers++; + } + } + + /* attach the cache and volume location */ +#ifdef AFS_CACHING_SUPPORT + cachefs_acquire_cookie(vlocation->cache, + &afs_vnode_cache_index_def, + volume, + &volume->cache); +#endif + + afs_get_vlocation(vlocation); + volume->vlocation = vlocation; + + vlocation->vols[type] = volume; + + success: + _debug("kAFS selected %s volume %08x", + afs_voltypes[volume->type], volume->vid); + *_volume = volume; + ret = 0; + + /* clean up */ + error_up: + up_write(&cell->vl_sem); + error: + afs_put_vlocation(vlocation); + afs_put_cell(cell); + + _leave(" = %d (%p)", ret, volume); + return ret; + + error_discard: + up_write(&cell->vl_sem); + + for (loop = volume->nservers - 1; loop >= 0; loop--) + afs_put_server(volume->servers[loop]); + + kfree(volume); + goto error; +} /* end afs_volume_lookup() */ + +/*****************************************************************************/ +/* + * destroy a volume record + */ +void afs_put_volume(struct afs_volume *volume) +{ + struct afs_vlocation *vlocation; + int loop; + + if (!volume) + return; + + _enter("%p", volume); + + vlocation = volume->vlocation; + + /* sanity check */ + BUG_ON(atomic_read(&volume->usage) <= 0); + + /* to prevent a race, the decrement and the dequeue must be effectively + * atomic */ + down_write(&vlocation->cell->vl_sem); + + if (likely(!atomic_dec_and_test(&volume->usage))) { + up_write(&vlocation->cell->vl_sem); + _leave(""); + return; + } + + vlocation->vols[volume->type] = NULL; + + up_write(&vlocation->cell->vl_sem); + + /* finish cleaning up the volume */ +#ifdef AFS_CACHING_SUPPORT + cachefs_relinquish_cookie(volume->cache, 0); +#endif + afs_put_vlocation(vlocation); + + for (loop = volume->nservers - 1; loop >= 0; loop--) + afs_put_server(volume->servers[loop]); + + kfree(volume); + + _leave(" [destroyed]"); +} /* end afs_put_volume() */ + +/*****************************************************************************/ +/* + * pick a server to use to try accessing this volume + * - returns with an elevated usage count on the server chosen + */ +int afs_volume_pick_fileserver(struct afs_volume *volume, + struct afs_server **_server) +{ + struct afs_server *server; + int ret, state, loop; + + _enter("%s", volume->vlocation->vldb.name); + + down_read(&volume->server_sem); + + /* handle the no-server case */ + if (volume->nservers == 0) { + ret = volume->rjservers ? -ENOMEDIUM : -ESTALE; + up_read(&volume->server_sem); + _leave(" = %d [no servers]", ret); + return ret; + } + + /* basically, just search the list for the first live server and use + * that */ + ret = 0; + for (loop = 0; loop < volume->nservers; loop++) { + server = volume->servers[loop]; + state = server->fs_state; + + switch (state) { + /* found an apparently healthy server */ + case 0: + afs_get_server(server); + up_read(&volume->server_sem); + *_server = server; + _leave(" = 0 (picked %08x)", + ntohl(server->addr.s_addr)); + return 0; + + case -ENETUNREACH: + if (ret == 0) + ret = state; + break; + + case -EHOSTUNREACH: + if (ret == 0 || + ret == -ENETUNREACH) + ret = state; + break; + + case -ECONNREFUSED: + if (ret == 0 || + ret == -ENETUNREACH || + ret == -EHOSTUNREACH) + ret = state; + break; + + default: + case -EREMOTEIO: + if (ret == 0 || + ret == -ENETUNREACH || + ret == -EHOSTUNREACH || + ret == -ECONNREFUSED) + ret = state; + break; + } + } + + /* no available servers + * - TODO: handle the no active servers case better + */ + up_read(&volume->server_sem); + _leave(" = %d", ret); + return ret; +} /* end afs_volume_pick_fileserver() */ + +/*****************************************************************************/ +/* + * release a server after use + * - releases the ref on the server struct that was acquired by picking + * - records result of using a particular server to access a volume + * - return 0 to try again, 1 if okay or to issue error + */ +int afs_volume_release_fileserver(struct afs_volume *volume, + struct afs_server *server, + int result) +{ + unsigned loop; + + _enter("%s,%08x,%d", + volume->vlocation->vldb.name, ntohl(server->addr.s_addr), + result); + + switch (result) { + /* success */ + case 0: + server->fs_act_jif = jiffies; + break; + + /* the fileserver denied all knowledge of the volume */ + case -ENOMEDIUM: + server->fs_act_jif = jiffies; + down_write(&volume->server_sem); + + /* first, find where the server is in the active list (if it + * is) */ + for (loop = 0; loop < volume->nservers; loop++) + if (volume->servers[loop] == server) + goto present; + + /* no longer there - may have been discarded by another op */ + goto try_next_server_upw; + + present: + volume->nservers--; + memmove(&volume->servers[loop], + &volume->servers[loop + 1], + sizeof(volume->servers[loop]) * + (volume->nservers - loop)); + volume->servers[volume->nservers] = NULL; + afs_put_server(server); + volume->rjservers++; + + if (volume->nservers > 0) + /* another server might acknowledge its existence */ + goto try_next_server_upw; + + /* handle the case where all the fileservers have rejected the + * volume + * - TODO: try asking the fileservers for volume information + * - TODO: contact the VL server again to see if the volume is + * no longer registered + */ + up_write(&volume->server_sem); + afs_put_server(server); + _leave(" [completely rejected]"); + return 1; + + /* problem reaching the server */ + case -ENETUNREACH: + case -EHOSTUNREACH: + case -ECONNREFUSED: + case -ETIMEDOUT: + case -EREMOTEIO: + /* mark the server as dead + * TODO: vary dead timeout depending on error + */ + spin_lock(&server->fs_lock); + if (!server->fs_state) { + server->fs_dead_jif = jiffies + HZ * 10; + server->fs_state = result; + printk("kAFS: SERVER DEAD state=%d\n", result); + } + spin_unlock(&server->fs_lock); + goto try_next_server; + + /* miscellaneous error */ + default: + server->fs_act_jif = jiffies; + case -ENOMEM: + case -ENONET: + break; + } + + /* tell the caller to accept the result */ + afs_put_server(server); + _leave(""); + return 1; + + /* tell the caller to loop around and try the next server */ + try_next_server_upw: + up_write(&volume->server_sem); + try_next_server: + afs_put_server(server); + _leave(" [try next server]"); + return 0; + +} /* end afs_volume_release_fileserver() */ + +/*****************************************************************************/ +/* + * match a volume hash record stored in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static cachefs_match_val_t afs_volume_cache_match(void *target, + const void *entry) +{ + const struct afs_cache_vhash *vhash = entry; + struct afs_volume *volume = target; + + _enter("{%u},{%u}", volume->type, vhash->vtype); + + if (volume->type == vhash->vtype) { + _leave(" = SUCCESS"); + return CACHEFS_MATCH_SUCCESS; + } + + _leave(" = FAILED"); + return CACHEFS_MATCH_FAILED; +} /* end afs_volume_cache_match() */ +#endif + +/*****************************************************************************/ +/* + * update a volume hash record stored in the cache + */ +#ifdef AFS_CACHING_SUPPORT +static void afs_volume_cache_update(void *source, void *entry) +{ + struct afs_cache_vhash *vhash = entry; + struct afs_volume *volume = source; + + _enter(""); + + vhash->vtype = volume->type; + +} /* end afs_volume_cache_update() */ +#endif diff --git a/fs/afs/volume.h b/fs/afs/volume.h new file mode 100644 index 00000000000..1e691889c4c --- /dev/null +++ b/fs/afs/volume.h @@ -0,0 +1,142 @@ +/* volume.h: AFS volume management + * + * Copyright (C) 2002 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. + */ + +#ifndef _LINUX_AFS_VOLUME_H +#define _LINUX_AFS_VOLUME_H + +#include "types.h" +#include "fsclient.h" +#include "kafstimod.h" +#include "kafsasyncd.h" +#include "cache.h" + +#define __packed __attribute__((packed)) + +typedef enum { + AFS_VLUPD_SLEEP, /* sleeping waiting for update timer to fire */ + AFS_VLUPD_PENDING, /* on pending queue */ + AFS_VLUPD_INPROGRESS, /* op in progress */ + AFS_VLUPD_BUSYSLEEP, /* sleeping because server returned EBUSY */ + +} __attribute__((packed)) afs_vlocation_upd_t; + +/*****************************************************************************/ +/* + * entry in the cached volume location catalogue + */ +struct afs_cache_vlocation +{ + uint8_t name[64]; /* volume name (lowercase, padded with NULs) */ + uint8_t nservers; /* number of entries used in servers[] */ + uint8_t vidmask; /* voltype mask for vid[] */ + uint8_t srvtmask[8]; /* voltype masks for servers[] */ +#define AFS_VOL_VTM_RW 0x01 /* R/W version of the volume is available (on this server) */ +#define AFS_VOL_VTM_RO 0x02 /* R/O version of the volume is available (on this server) */ +#define AFS_VOL_VTM_BAK 0x04 /* backup version of the volume is available (on this server) */ + + afs_volid_t vid[3]; /* volume IDs for R/W, R/O and Bak volumes */ + struct in_addr servers[8]; /* fileserver addresses */ + time_t rtime; /* last retrieval time */ +}; + +#ifdef AFS_CACHING_SUPPORT +extern struct cachefs_index_def afs_vlocation_cache_index_def; +#endif + +/*****************************************************************************/ +/* + * volume -> vnode hash table entry + */ +struct afs_cache_vhash +{ + afs_voltype_t vtype; /* which volume variation */ + uint8_t hash_bucket; /* which hash bucket this represents */ +} __attribute__((packed)); + +#ifdef AFS_CACHING_SUPPORT +extern struct cachefs_index_def afs_volume_cache_index_def; +#endif + +/*****************************************************************************/ +/* + * AFS volume location record + */ +struct afs_vlocation +{ + atomic_t usage; + struct list_head link; /* link in cell volume location list */ + struct afs_timer timeout; /* decaching timer */ + struct afs_cell *cell; /* cell to which volume belongs */ +#ifdef AFS_CACHING_SUPPORT + struct cachefs_cookie *cache; /* caching cookie */ +#endif + struct afs_cache_vlocation vldb; /* volume information DB record */ + struct afs_volume *vols[3]; /* volume access record pointer (index by type) */ + rwlock_t lock; /* access lock */ + unsigned long read_jif; /* time at which last read from vlserver */ + struct afs_timer upd_timer; /* update timer */ + struct afs_async_op upd_op; /* update operation */ + afs_vlocation_upd_t upd_state; /* update state */ + unsigned short upd_first_svix; /* first server index during update */ + unsigned short upd_curr_svix; /* current server index during update */ + unsigned short upd_rej_cnt; /* ENOMEDIUM count during update */ + unsigned short upd_busy_cnt; /* EBUSY count during update */ + unsigned short valid; /* T if valid */ +}; + +extern int afs_vlocation_lookup(struct afs_cell *cell, + const char *name, + unsigned namesz, + struct afs_vlocation **_vlocation); + +#define afs_get_vlocation(V) do { atomic_inc(&(V)->usage); } while(0) + +extern void afs_put_vlocation(struct afs_vlocation *vlocation); +extern void afs_vlocation_do_timeout(struct afs_vlocation *vlocation); + +/*****************************************************************************/ +/* + * AFS volume access record + */ +struct afs_volume +{ + atomic_t usage; + struct afs_cell *cell; /* cell to which belongs (unrefd ptr) */ + struct afs_vlocation *vlocation; /* volume location */ +#ifdef AFS_CACHING_SUPPORT + struct cachefs_cookie *cache; /* caching cookie */ +#endif + afs_volid_t vid; /* volume ID */ + afs_voltype_t __packed type; /* type of volume */ + char type_force; /* force volume type (suppress R/O -> R/W) */ + unsigned short nservers; /* number of server slots filled */ + unsigned short rjservers; /* number of servers discarded due to -ENOMEDIUM */ + struct afs_server *servers[8]; /* servers on which volume resides (ordered) */ + struct rw_semaphore server_sem; /* lock for accessing current server */ +}; + +extern int afs_volume_lookup(const char *name, + struct afs_cell *cell, + int rwpath, + struct afs_volume **_volume); + +#define afs_get_volume(V) do { atomic_inc(&(V)->usage); } while(0) + +extern void afs_put_volume(struct afs_volume *volume); + +extern int afs_volume_pick_fileserver(struct afs_volume *volume, + struct afs_server **_server); + +extern int afs_volume_release_fileserver(struct afs_volume *volume, + struct afs_server *server, + int result); + +#endif /* _LINUX_AFS_VOLUME_H */ |