]> git.hungrycats.org Git - linux/commitdiff
[PATCH] AFS filesystem 2/2
authorDavid Howells <dhowells@redhat.com>
Tue, 15 Oct 2002 12:47:34 +0000 (05:47 -0700)
committerLinus Torvalds <torvalds@home.transmeta.com>
Tue, 15 Oct 2002 12:47:34 +0000 (05:47 -0700)
Here's a patch to add an Andrew File System (AFS) driver to the kernel.
Currently it only provides read-only, uncached, non-automounted and
unsecured support.

40 files changed:
Documentation/filesystems/afs.txt [new file with mode: 0644]
fs/Config.help
fs/Config.in
fs/Makefile
fs/afs/Makefile [new file with mode: 0644]
fs/afs/cache-layout.h [new file with mode: 0644]
fs/afs/callback.c [new file with mode: 0644]
fs/afs/cell.c [new file with mode: 0644]
fs/afs/cell.h [new file with mode: 0644]
fs/afs/cmservice.c [new file with mode: 0644]
fs/afs/cmservice.h [new file with mode: 0644]
fs/afs/dir.c [new file with mode: 0644]
fs/afs/errors.h [new file with mode: 0644]
fs/afs/file.c [new file with mode: 0644]
fs/afs/fsclient.c [new file with mode: 0644]
fs/afs/fsclient.h [new file with mode: 0644]
fs/afs/inode.c [new file with mode: 0644]
fs/afs/internal.h [new file with mode: 0644]
fs/afs/kafsasyncd.c [new file with mode: 0644]
fs/afs/kafsasyncd.h [new file with mode: 0644]
fs/afs/kafstimod.c [new file with mode: 0644]
fs/afs/kafstimod.h [new file with mode: 0644]
fs/afs/main.c [new file with mode: 0644]
fs/afs/misc.c [new file with mode: 0644]
fs/afs/mntpt.c [new file with mode: 0644]
fs/afs/mount.h [new file with mode: 0644]
fs/afs/proc.c [new file with mode: 0644]
fs/afs/server.c [new file with mode: 0644]
fs/afs/server.h [new file with mode: 0644]
fs/afs/super.c [new file with mode: 0644]
fs/afs/super.h [new file with mode: 0644]
fs/afs/transport.h [new file with mode: 0644]
fs/afs/types.h [new file with mode: 0644]
fs/afs/vlclient.c [new file with mode: 0644]
fs/afs/vlclient.h [new file with mode: 0644]
fs/afs/vlocation.c [new file with mode: 0644]
fs/afs/vnode.c [new file with mode: 0644]
fs/afs/vnode.h [new file with mode: 0644]
fs/afs/volume.c [new file with mode: 0644]
fs/afs/volume.h [new file with mode: 0644]

diff --git a/Documentation/filesystems/afs.txt b/Documentation/filesystems/afs.txt
new file mode 100644 (file)
index 0000000..2f4237d
--- /dev/null
@@ -0,0 +1,155 @@
+                            kAFS: AFS FILESYSTEM
+                            ====================
+
+ABOUT
+=====
+
+This filesystem provides a fairly simple AFS filesystem driver. It is under
+development and only provides very basic facilities. It does not yet support
+the following AFS features:
+
+       (*) Write support.
+       (*) Communications security.
+       (*) Local caching.
+       (*) pioctl() system call.
+       (*) Automatic mounting of embedded mountpoints.
+
+
+USAGE
+=====
+
+When inserting the driver modules the root cell must be specified along with a
+list of volume location server IP addresses:
+
+       insmod rxrpc.o
+       insmod kafs.o rootcell=cambridge.redhat.com:172.16.18.73:172.16.18.91
+
+The first module is a driver for the RxRPC remote operation protocol, and the
+second is the actual filesystem driver for the AFS filesystem.
+
+Once the module has been loaded, more modules can be added by the following
+procedure:
+
+       echo add grand.central.org 18.7.14.88:128.2.191.224 >/proc/fs/afs/cells
+
+Where the parameters to the "add" command are the name of a cell and a list of
+volume location servers within that cell.
+
+Filesystems can be mounted anywhere by commands similar to the following:
+
+       mount -t afs "%cambridge.redhat.com:root.afs." /afs
+       mount -t afs "#cambridge.redhat.com:root.cell." /afs/cambridge
+       mount -t afs "#root.afs." /afs
+       mount -t afs "#root.cell." /afs/cambridge
+
+  NB: When using this on Linux 2.4, the mount command has to be different,
+      since the filesystem doesn't have access to the device name argument:
+
+       mount -t afs none /afs -ovol="#root.afs."
+
+Where the initial character is either a hash or a percent symbol depending on
+whether you definitely want a R/W volume (hash) or whether you'd prefer a R/O
+volume, but are willing to use a R/W volume instead (percent).
+
+The name of the volume can be suffixes with ".backup" or ".readonly" to
+specify connection to only volumes of those types.
+
+The name of the cell is optional, and if not given during a mount, then the
+named volume will be looked up in the cell specified during insmod.
+
+Additional cells can be added through /proc (see later section).
+
+
+MOUNTPOINTS
+===========
+
+AFS has a concept of mountpoints. These are specially formatted symbolic links
+(of the same form as the "device name" passed to mount). kAFS presents these
+to the user as directories that have special properties:
+
+  (*) They cannot be listed. Running a program like "ls" on them will incur an
+      EREMOTE error (Object is remote).
+
+  (*) Other objects can't be looked up inside of them. This also incurs an
+      EREMOTE error.
+
+  (*) They can be queried with the readlink() system call, which will return
+      the name of the mountpoint to which they point. The "readlink" program
+      will also work.
+
+  (*) They can be mounted on (which symbolic links can't).
+
+
+PROC FILESYSTEM
+===============
+
+The rxrpc module creates a number of files in various places in the /proc
+filesystem:
+
+  (*) Firstly, some information files are made available in a directory called
+      "/proc/net/rxrpc/". These list the extant transport endpoint, peer,
+      connection and call records.
+
+  (*) Secondly, some control files are made available in a directory called
+      "/proc/sys/rxrpc/". Currently, all these files can be used for is to
+      turn on various levels of tracing.
+
+The AFS modules creates a "/proc/fs/afs/" directory and populates it:
+
+  (*) A "cells" file that lists cells currently known to the afs module.
+
+  (*) A directory per cell that contains files that list volume location
+      servers, volumes, and active servers known within that cell.
+
+
+THE CELL DATABASE
+=================
+
+The filesystem maintains an internal database of all the cells it knows and
+the IP addresses of the volume location servers for those cells. The cell to
+which the computer belongs is added to the database when insmod is performed
+by the "rootcell=" argument.
+
+Further cells can be added by commands similar to the following:
+
+       echo add CELLNAME VLADDR[:VLADDR][:VLADDR]... >/proc/fs/afs/cells
+       echo add grand.central.org 18.7.14.88:128.2.191.224 >/proc/fs/afs/cells
+
+No other cell database operations are available at this time.
+
+
+EXAMPLES
+========
+
+Here's what I use to test this. Some of the names and IP addresses are local
+to my internal DNS. My "root.afs" partition has a mount point within it for
+some public volumes volumes.
+
+insmod -S /tmp/rxrpc.o 
+insmod -S /tmp/kafs.o rootcell=cambridge.redhat.com:172.16.18.73:172.16.18.91
+
+mount -t afs \%root.afs. /afs
+mount -t afs \%cambridge.redhat.com:root.cell. /afs/cambridge.redhat.com/
+
+echo add grand.central.org 18.7.14.88:128.2.191.224 > /proc/fs/afs/cells 
+mount -t afs "#grand.central.org:root.cell." /afs/grand.central.org/
+mount -t afs "#grand.central.org:root.archive." /afs/grand.central.org/archive
+mount -t afs "#grand.central.org:root.contrib." /afs/grand.central.org/contrib
+mount -t afs "#grand.central.org:root.doc." /afs/grand.central.org/doc
+mount -t afs "#grand.central.org:root.project." /afs/grand.central.org/project
+mount -t afs "#grand.central.org:root.service." /afs/grand.central.org/service
+mount -t afs "#grand.central.org:root.software." /afs/grand.central.org/software
+mount -t afs "#grand.central.org:root.user." /afs/grand.central.org/user
+
+umount /afs/grand.central.org/user
+umount /afs/grand.central.org/software
+umount /afs/grand.central.org/service
+umount /afs/grand.central.org/project
+umount /afs/grand.central.org/doc
+umount /afs/grand.central.org/contrib
+umount /afs/grand.central.org/archive
+umount /afs/grand.central.org
+umount /afs/cambridge.redhat.com
+umount /afs
+rmmod kafs
+rmmod rxrpc
index d8492860bf9ceae1c9ac429de8ba332c07a78c86..76ffd7584addadbe2e7963214ad0566f80a50f26 100644 (file)
@@ -1144,3 +1144,11 @@ CONFIG_XFS_RT
 
   If unsure, say N.
 
+CONFIG_AFS_FS
+  If you say Y here, you will get an experimental Andrew File System
+  driver. It currently only supports unsecured read-only AFS access.
+
+  See Documentation/filesystems/afs.txt for more intormation.
+
+  If unsure, say N.
+
index b2c37c7419b9bf35265dd381f9fc640dee43b33c..0464a17a8dbd86771f28a848a192fefbe42511b6 100644 (file)
@@ -158,6 +158,16 @@ if [ "$CONFIG_NET" = "y" ]; then
       # for fs/nls/Config.in
       define_bool CONFIG_NCPFS_NLS n
    fi
+
+   dep_tristate 'Andrew File System support (AFS) (Experimental)' CONFIG_AFS_FS $CONFIG_INET $CONFIG_EXPERIMENTAL
+   if [ "$CONFIG_AFS_FS" = "y" ]; then
+      define_tristate CONFIG_RXRPC y
+   else
+      if [ "$CONFIG_AFS_FS" = "m" ]; then
+        define_tristate CONFIG_RXRPC m
+      fi
+   fi
+
    endmenu
 
 else
index a4320cf860acbd30fcc69a815fc6d37f0249a716..c28d57ab55a9f62578a9492f81b5b07f4f21735c 100644 (file)
@@ -86,5 +86,6 @@ obj-$(CONFIG_REISERFS_FS)     += reiserfs/
 obj-$(CONFIG_SUN_OPENPROMFS)   += openpromfs/
 obj-$(CONFIG_JFS_FS)           += jfs/
 obj-$(CONFIG_XFS_FS)           += xfs/
+obj-$(CONFIG_AFS_FS)           += afs/
 
 include $(TOPDIR)/Rules.make
diff --git a/fs/afs/Makefile b/fs/afs/Makefile
new file mode 100644 (file)
index 0000000..753cf8c
--- /dev/null
@@ -0,0 +1,36 @@
+#
+# Makefile for Red Hat Linux AFS client.
+#
+
+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
+
+#      cache.o
+
+obj-m  := kafs.o
+
+# superfluous for 2.5, but needed for 2.4..
+ifeq "$(VERSION).$(PATCHLEVEL)" "2.4"
+kafs.o: $(kafs-objs)
+       $(LD) -r -o kafs.o $(kafs-objs)
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/fs/afs/cache-layout.h b/fs/afs/cache-layout.h
new file mode 100644 (file)
index 0000000..e71afd7
--- /dev/null
@@ -0,0 +1,224 @@
+/* cache-layout.h: AFS cache layout
+ *
+ * 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 cache is stored on a block device and is laid out as:
+ *
+ *  0  +------------------------------------------------
+ *     |
+ *     |  SuperBlock
+ *     |
+ *  1  +------------------------------------------------
+ *     |
+ *     |  file-meta-data File: Data block #0
+ *     |  - file-meta-data file (volix #0 file #0) : Meta-data block
+ *     |    - contains direct pointers to first 64 file data blocks
+ *     |  - Cached cell catalogue file (volix #0 file #1) file: Meta-data block
+ *     |  - Cached volume location catalogue file (volix #0 file #2): Meta-data block
+ *     |  - Vnode catalogue hash bucket #n file: Meta-data block
+ *     |
+ *  2  +------------------------------------------------
+ *     |
+ *     |  Bitmap Block Allocation Bitmap
+ *     |  - 1 bit per block in the bitmap block
+ *      |  - bit 0 of dword 0 refers to the bitmap block 0
+ *     |    - set if the bitmap block is full
+ *      |  - 32768 bits per block, requiring 4 blocks for a 16Tb cache
+ *     |  - bitmap bitmap blocks are cleared initially
+ *     |  - not present if <4 bitmap blocks
+ *     |
+ *     +------------------------------------------------
+ *     |
+ *     |  File Block Allocation Bitmap
+ *     |  - 1 bit per block in the cache
+ *      |  - bit 0 of dword 0 refers to the first block of the data cache
+ *     |    - set if block is allocated
+ *      |  - 32768 bits per block, requiring 131072 blocks for a 16Tb cache
+ *     |  - bitmap blocks are cleared lazily (sb->bix_bitmap_unready)
+ *     |
+ *     +------------------------------------------------
+ *     |
+ *     |  Data Cache
+ *     |
+ *  End        +------------------------------------------------
+ *
+ * Blocks are indexed by an unsigned 32-bit word, meaning that the cache can hold up to 2^32 pages,
+ * or 16Tb in total.
+ *
+ * Credentials will be cached in memory, since they are subject to change without notice, and are
+ * difficult to derive manually, being constructed from the following information:
+ * - per vnode user ID and mode mask
+ * - parent directory ACL
+ * - directory ACL (dirs only)
+ * - group lists from ptserver
+ */
+
+#ifndef _LINUX_AFS_CACHE_LAYOUT_H
+#define _LINUX_AFS_CACHE_LAYOUT_H
+
+#include "types.h"
+
+typedef u32 afsc_blockix_t;
+typedef u32 afsc_cellix_t;
+
+/* Cached volume index
+ * - afsc_volix_t/4 is the index into the volume cache
+ * - afsc_volix_t%4 is 0 for R/W, 1 for R/O and 2 for Bak (3 is not used)
+ * - afsc_volix_t==0-3 refers to a "virtual" volume that stores meta-data about the cache
+ */
+typedef struct {
+       u32 index;
+} afsc_volix_t;
+
+#define AFSC_VNCAT_HASH_NBUCKETS       128
+
+/* special meta file IDs (all cell 0 vol 0) */
+enum afsc_meta_fids {
+       AFSC_META_FID_METADATA          = 0,
+       AFSC_META_FID_CELL_CATALOGUE    = 1,
+       AFSC_META_FID_VLDB_CATALOGUE    = 2,
+       AFSC_META_FID_VNODE_CATALOGUE0  = 3,
+       AFSC_META_FID__COUNT            = AFSC_VNCAT_HASH_NBUCKETS + 3
+};
+
+/*****************************************************************************/
+/*
+ * cache superblock block layout
+ * - the blockdev is prepared for initialisation by 'echo "kafsuninit" >/dev/hdaXX' before mounting
+ * - when initialised, the magic number is changed to "kafs-cache"
+ */
+struct afsc_super_block
+{
+       char                    magic[10];      /* magic number */
+#define AFSC_SUPER_MAGIC "kafs-cache"
+#define AFSC_SUPER_MAGIC_NEEDS_INIT "kafsuninit"
+#define AFSC_SUPER_MAGIC_SIZE 10
+
+       unsigned short          endian;         /* 0x1234 stored CPU-normal order */
+#define AFSC_SUPER_ENDIAN 0x1234
+
+       unsigned                version;        /* format version */
+#define AFSC_SUPER_VERSION 1
+
+       /* layout */
+       unsigned                bsize;                  /* cache block size */
+       afsc_blockix_t          bix_bitmap_fullmap;     /* block ix of bitmap full bitmap */
+       afsc_blockix_t          bix_bitmap;             /* block ix of alloc bitmap */
+       afsc_blockix_t          bix_bitmap_unready;     /* block ix of unready area of bitmap */
+       afsc_blockix_t          bix_cache;              /* block ix of data cache */
+       afsc_blockix_t          bix_end;                /* block ix of end of cache */
+};
+
+/*****************************************************************************/
+/*
+ * vnode (inode) metadata cache record
+ * - padded out to 512 bytes and stored eight to a page
+ * - only the data version is necessary
+ *   - disconnected operation is not supported
+ *   - afs_iget() contacts the server to get the meta-data _anyway_ when an inode is first brought
+ *     into memory
+ * - at least 64 direct block pointers will be available (a directory is max 256Kb)
+ * - any block pointer which is 0 indicates an uncached page
+ */
+struct afsc_vnode_meta
+{
+       /* file ID */
+       afsc_volix_t            volume_ix;      /* volume catalogue index */
+       unsigned                vnode;          /* vnode number */
+       unsigned                unique;         /* FID unique */
+       unsigned                size;           /* size of file */
+       time_t                  mtime;          /* last modification time */
+
+       /* file status */
+       afs_dataversion_t       version;        /* current data version */
+
+       /* file contents */
+       afsc_blockix_t          dbl_indirect;   /* double indirect block index */
+       afsc_blockix_t          indirect;       /* single indirect block 0 index */
+       afsc_blockix_t          direct[0];      /* direct block index (#AFSC_VNODE_META_DIRECT) */
+};
+
+#define AFSC_VNODE_META_RECSIZE        512     /* record size */
+
+#define AFSC_VNODE_META_DIRECT \
+       ((AFSC_VNODE_META_RECSIZE-sizeof(struct afsc_vnode_meta))/sizeof(afsc_blockix_t))
+
+#define AFSC_VNODE_META_PER_PAGE       (PAGE_SIZE / AFSC_VNODE_META_RECSIZE)
+
+/*****************************************************************************/
+/*
+ * entry in the cached cell catalogue
+ */
+struct afsc_cell_record
+{
+       char                    name[64];       /* cell name (padded with NULs) */
+       struct in_addr          servers[16];    /* cached cell servers */
+};
+
+/*****************************************************************************/
+/*
+ * entry in the cached volume location catalogue
+ * - indexed by afsc_volix_t/4
+ */
+struct afsc_vldb_record
+{
+       char                    name[64];       /* volume name (padded with NULs) */
+       afs_volid_t             vid[3];         /* volume IDs for R/W, R/O and Bak volumes */
+       unsigned char           vidmask;        /* voltype mask for vid[] */
+       unsigned char           _pad[1];
+       unsigned short          nservers;       /* number of entries used in servers[] */
+       struct in_addr          servers[8];     /* fileserver addresses */
+       unsigned char           srvtmask[8];    /* voltype masks for servers[] */
+#define AFSC_VOL_STM_RW        0x01 /* server holds a R/W version of the volume */
+#define AFSC_VOL_STM_RO        0x02 /* server holds a R/O version of the volume */
+#define AFSC_VOL_STM_BAK       0x04 /* server holds a backup version of the volume */
+
+       afsc_cellix_t           cell_ix;        /* cell catalogue index (MAX_UINT if unused) */
+       time_t                  ctime;          /* time at which cached */
+};
+
+/*****************************************************************************/
+/*
+ * vnode catalogue entry
+ * - must be 2^x size so that do_generic_file_read doesn't present them split across pages
+ */
+struct afsc_vnode_catalogue
+{
+       afsc_volix_t            volume_ix;      /* volume catalogue index */
+       afs_vnodeid_t           vnode;          /* vnode ID */
+       u32                     meta_ix;        /* metadata file index */
+       u32                     atime;          /* last time entry accessed */
+} __attribute__((packed));
+
+#define AFSC_VNODE_CATALOGUE_PER_BLOCK ((size_t)(PAGE_SIZE/sizeof(struct afsc_vnode_catalogue)))
+
+/*****************************************************************************/
+/*
+ * vnode data "page directory" block
+ * - first 1024 pages don't map through here
+ * - PAGE_SIZE in size
+ */
+struct afsc_indirect_block
+{
+       afsc_blockix_t          pt_bix[1024];   /* "page table" block indices */
+};
+
+/*****************************************************************************/
+/*
+ * vnode data "page table" block
+ * - PAGE_SIZE in size
+ */
+struct afsc_dbl_indirect_block
+{
+       afsc_blockix_t          page_bix[1024]; /* "page" block indices */
+};
+
+
+#endif /* _LINUX_AFS_CACHE_LAYOUT_H */
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
new file mode 100644 (file)
index 0000000..8d030bd
--- /dev/null
@@ -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(afs_server_t *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 inode *inode;
+               afs_vnode_t *vnode;
+
+               vnode = list_entry(callbacks.next,afs_vnode_t,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);
+                       if (release) 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(afs_server_t *server, size_t count, afs_callback_t callbacks[])
+{
+       struct list_head *_p;
+
+       _enter("%p,%u,",server,count);
+
+       for (; count>0; callbacks++, count--) {
+               struct inode *inode = NULL;
+               afs_vnode_t *vnode = 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(_p,&afs_cb_hash(server,&callbacks->fid)) {
+                       vnode = list_entry(_p,afs_vnode_t,cb_hash_link);
+
+                       if (memcmp(&vnode->fid,&callbacks->fid,sizeof(afs_fid_t))!=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_inode_pages(inode->i_mapping);
+                               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(afs_server_t *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 (file)
index 0000000..f7f00a2
--- /dev/null
@@ -0,0 +1,452 @@
+/* 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 rwlock_t afs_cells_lock = RW_LOCK_UNLOCKED;
+static DECLARE_RWSEM(afs_cells_sem); /* add/remove serialisation */
+static afs_cell_t *afs_cell_root;
+
+static char *rootcell;
+
+MODULE_PARM(rootcell,"s");
+MODULE_PARM_DESC(rootcell,"root AFS cell name and VL server IP addr list");
+
+/*****************************************************************************/
+/*
+ * 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, afs_cell_t **_cell)
+{
+       afs_cell_t *cell;
+       char *next;
+       int ret;
+
+       _enter("%s",name);
+
+       if (!name) BUG(); /* TODO: want to look up "this cell" in the cache */
+
+       down_write(&afs_cells_sem);
+
+       /* allocate and initialise a cell record */
+       cell = kmalloc(sizeof(afs_cell_t) + strlen(name) + 1,GFP_KERNEL);
+       if (!cell) {
+               _leave(" = -ENOMEM");
+               return -ENOMEM;
+       }
+
+       memset(cell,0,sizeof(afs_cell_t));
+       atomic_set(&cell->usage,0);
+
+       INIT_LIST_HEAD(&cell->link);
+       INIT_LIST_HEAD(&cell->caches);
+
+       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>=16)
+                       break;
+
+       } while(vllist=next, vllist);
+
+       /* add a proc dir for this cell */
+       ret = afs_proc_cell_setup(cell);
+       if (ret<0)
+               goto error;
+
+       /* 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("kAFS: bad VL server IP address: '%s'\n",vllist);
+ error:
+       up_write(&afs_cells_sem);
+       kfree(afs_cell_root);
+       return ret;
+} /* end afs_cell_create() */
+
+/*****************************************************************************/
+/*
+ * initialise the cell database from module parameters
+ */
+int afs_cell_init(void)
+{
+       char *cp;
+       int ret;
+
+       _enter("");
+
+       if (!rootcell) {
+               printk("kAFS: no root cell specified\n");
+               return -EINVAL;
+       }
+
+       cp = strchr(rootcell,':');
+       if (!cp) {
+               printk("kAFS: no VL server IP addresses specified\n");
+               return -EINVAL;
+       }
+
+       /* allocate a cell record for the root cell */
+       *cp++ = 0;
+       ret = afs_cell_create(rootcell,cp,&afs_cell_root);
+       if (ret==0)
+               afs_get_cell(afs_cell_root);
+
+       _leave(" = %d",ret);
+       return ret;
+
+} /* end afs_cell_init() */
+
+/*****************************************************************************/
+/*
+ * lookup a cell record
+ */
+int afs_cell_lookup(const char *name, afs_cell_t **_cell)
+{
+       struct list_head *_p;
+       afs_cell_t *cell;
+
+       _enter("\"%s\",",name?name:"*thiscell*");
+
+       cell = afs_cell_root;
+
+       if (name) {
+               /* if the cell was named, look for it in the cell record list */
+               cell = NULL;
+               read_lock(&afs_cells_lock);
+
+               list_for_each(_p,&afs_cells) {
+                       cell = list_entry(_p,afs_cell_t,link);
+                       if (strcmp(cell->name,name)==0)
+                               break;
+                       cell = NULL;
+               }
+
+               read_unlock(&afs_cells_lock);
+       }
+
+       if (cell)
+               afs_get_cell(cell);
+
+       *_cell = cell;
+       _leave(" = %d (%p)",cell?0:-ENOENT,cell);
+       return cell ? 0 : -ENOENT;
+
+} /* end afs_cell_lookup() */
+
+/*****************************************************************************/
+/*
+ * try and get a cell record
+ */
+afs_cell_t *afs_get_cell_maybe(afs_cell_t **_cell)
+{
+       afs_cell_t *cell;
+
+       write_lock(&afs_cells_lock);
+
+       cell = *_cell;
+       if (cell && !list_empty(&cell->link))
+               atomic_inc(&cell->usage);
+       else 
+               cell = NULL;
+
+       write_unlock(&afs_cells_lock);
+
+       return cell;
+} /* end afs_get_cell_maybe() */
+
+/*****************************************************************************/
+/*
+ * destroy a cell record
+ */
+void afs_put_cell(afs_cell_t *cell)
+{
+       _enter("%p{%d,%s}",cell,atomic_read(&cell->usage),cell->name);
+
+       /* sanity check */
+       if (atomic_read(&cell->usage)<=0)
+               BUG();
+
+       /* 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);
+
+       if (!list_empty(&cell->sv_list))        BUG();
+       if (!list_empty(&cell->sv_graveyard))   BUG();
+       if (!list_empty(&cell->vl_list))        BUG();
+       if (!list_empty(&cell->vl_graveyard))   BUG();
+
+       _leave(" [unused]");
+} /* end afs_put_cell() */
+
+/*****************************************************************************/
+/*
+ * destroy a cell record
+ */
+static void afs_cell_destroy(afs_cell_t *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 */
+       if (atomic_read(&cell->usage)!=0)
+               BUG();
+
+       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);
+
+       up_write(&afs_cells_sem);
+
+       if (!list_empty(&cell->sv_list))        BUG();
+       if (!list_empty(&cell->sv_graveyard))   BUG();
+       if (!list_empty(&cell->vl_list))        BUG();
+       if (!list_empty(&cell->vl_graveyard))   BUG();
+
+       /* 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, afs_server_t **_server)
+{
+       struct list_head *_pc, *_ps;
+       afs_server_t *server;
+       afs_cell_t *cell;
+
+       _enter("%p{a=%08x},",peer,ntohl(peer->addr.s_addr));
+
+       /* search the cell list */
+       read_lock(&afs_cells_lock);
+
+       list_for_each(_pc,&afs_cells) {
+               cell = list_entry(_pc,afs_cell_t,link);
+
+               _debug("? cell %s",cell->name);
+
+               write_lock(&cell->sv_lock);
+
+               /* check the active list */
+               list_for_each(_ps,&cell->sv_list) {
+                       server = list_entry(_ps,afs_server_t,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(_ps,&cell->sv_graveyard) {
+                       server = list_entry(_ps,afs_server_t,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
+ * - the timeout daemon is stopped before calling this
+ */
+void afs_cell_purge(void)
+{
+       afs_vlocation_t *vlocation;
+       afs_cell_t *cell;
+
+       _enter("");
+
+       if (afs_cell_root)
+               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,afs_cell_t,link);
+                       list_del_init(&cell->link);
+               }
+
+               write_unlock(&afs_cells_lock);
+
+               if (cell) {
+                       _debug("PURGING CELL %s (%d)",cell->name,atomic_read(&cell->usage));
+
+                       if (!list_empty(&cell->sv_list)) BUG();
+                       if (!list_empty(&cell->vl_list)) BUG();
+
+                       /* 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,
+                                                      afs_vlocation_t,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)) {
+                               afs_server_t *server;
+
+                               server = list_entry(cell->sv_graveyard.next,afs_server_t,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() */
diff --git a/fs/afs/cell.h b/fs/afs/cell.h
new file mode 100644 (file)
index 0000000..48eb9fa
--- /dev/null
@@ -0,0 +1,63 @@
+/* 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"
+
+extern volatile int afs_cells_being_purged; /* T when cells are being purged by rmmod */
+
+/*****************************************************************************/
+/*
+ * 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 */
+       struct list_head        caches;         /* list of caches currently backing this cell */
+
+       /* 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[16];   /* cell VL server addresses */
+
+       char                    name[0];        /* cell name - must go last */
+};
+
+extern int afs_cell_init(void);
+
+extern int afs_cell_create(const char *name, char *vllist, afs_cell_t **_cell);
+
+extern int afs_cell_lookup(const char *name, afs_cell_t **_cell);
+
+#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0)
+
+extern afs_cell_t *afs_get_cell_maybe(afs_cell_t **_cell);
+
+extern void afs_put_cell(afs_cell_t *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 (file)
index 0000000..b95c362
--- /dev/null
@@ -0,0 +1,639 @@
+/* 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/version.h>
+#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 spinlock_t afscm_calls_lock = SPIN_LOCK_UNLOCKED;
+static spinlock_t kafscmd_attention_lock = SPIN_LOCK_UNLOCKED;
+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);
+       strcpy(current->comm,"kafscmd");
+
+       daemonize();
+
+       complete(&kafscmd_alive);
+
+       /* only certain signals are of interest */
+       spin_lock_irq(&current->sig->siglock);
+       siginitsetinv(&current->blocked,0);
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,3)
+       recalc_sigpending();
+#else
+       recalc_sigpending(current);
+#endif
+       spin_unlock_irq(&current->sig->siglock);
+
+       /* 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;
+       }
+
+       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);
+
+       if (afscm_usage==0) BUG();
+       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 */
+
+                       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);
+       }
+
+       up_write(&afscm_sem);
+
+} /* end afscm_stop() */
+
+/*****************************************************************************/
+/*
+ * handle the fileserver breaking a set of callbacks
+ */
+static void _SRXAFSCM_CallBack(struct rxrpc_call *call)
+{
+       afs_server_t *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;
+
+               {
+                       afs_callback_t *cb, *pcb;
+                       int loop;
+                       u32 *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,afs_callback_t);
+
+                       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);
+
+       if (server) 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)
+{
+       afs_server_t *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);
+
+       if (server) afs_put_server(server);
+
+       _leave(" = %d",ret);
+
+} /* end _SRXAFSCM_InitCallBackState() */
+
+/*****************************************************************************/
+/*
+ * handle a probe from a fileserver
+ */
+static void _SRXAFSCM_Probe(struct rxrpc_call *call)
+{
+       afs_server_t *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);
+
+       if (server) 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 (file)
index 0000000..89fb14e
--- /dev/null
@@ -0,0 +1,27 @@
+/* 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(afs_server_t *server);
+extern int SRXAFSCM_CallBack(afs_server_t *server, size_t count, afs_callback_t callbacks[]);
+extern int SRXAFSCM_Probe(afs_server_t *server);
+
+#endif /* _LINUX_AFS_CMSERVICE_H */
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
new file mode 100644 (file)
index 0000000..d39345f
--- /dev/null
@@ -0,0 +1,642 @@
+/* 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);
+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, int flags);
+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,
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       .getattr        = afs_inode_getattr,
+#else
+       .revalidate     = afs_inode_revalidate,
+#endif
+//     .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,
+};
+
+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
+
+typedef union afs_dirent {
+       struct {
+               u8      valid;
+               u8      unused[1];
+               u16     hash_next;
+               u32     vnode;
+               u32     unique;
+               u8      name[16];
+               u8      overflow[4];    /* if any char of the name (inc NUL) reaches here, consume
+                                        * the next dirent too */
+       };
+       u8      extended_name[32];
+} afs_dirent_t;
+
+/* AFS directory page header (one at the beginning of every 2048-byte chunk) */
+typedef struct afs_dir_pagehdr {
+       u16     npages;
+       u16     magic;
+#define AFS_DIR_MAGIC htons(1234)
+       u8      nentries;
+       u8      bitmap[8];
+       u8      pad[19];
+} afs_dir_pagehdr_t;
+
+/* directory block layout */
+typedef union afs_dir_block {
+
+       afs_dir_pagehdr_t pagehdr;
+
+       struct {
+               afs_dir_pagehdr_t pagehdr;
+               u8              alloc_ctrs[128];
+               u16             hashtable[AFS_DIR_HASHTBL_SIZE]; /* dir hash table */
+       } hdr;
+
+       afs_dirent_t dirents[AFS_DIRENT_PER_BLOCK];
+} afs_dir_block_t;
+
+/* layout on a linux VM page */
+typedef struct afs_dir_page {
+       afs_dir_block_t blocks[PAGE_SIZE/sizeof(afs_dir_block_t)];
+} afs_dir_page_t;
+
+struct afs_dir_lookup_cookie {
+       afs_fid_t       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)
+{
+       afs_dir_page_t *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(afs_dir_block_t);
+
+       /* 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);
+
+       if (sizeof(afs_dir_block_t) != 2048) BUG();
+       if (sizeof(afs_dirent_t) != 32) BUG();
+
+       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,
+                                   afs_dir_block_t *block,
+                                   unsigned blkoff,
+                                   void *cookie,
+                                   filldir_t filldir)
+{
+       afs_dirent_t *dire;
+       unsigned offset, next, curr;
+       size_t nlen;
+       int tmp, ret;
+
+       _enter("%u,%x,%p,,",*fpos,blkoff,block);
+
+       curr = (*fpos - blkoff) / sizeof(afs_dirent_t);
+
+       /* 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[%u.%u]: unused\n",blkoff/sizeof(afs_dir_block_t),offset);
+                       if (offset>=curr)
+                               *fpos = blkoff + next * sizeof(afs_dirent_t);
+                       continue;
+               }
+
+               /* got a valid entry */
+               dire = &block->dirents[offset];
+               nlen = strnlen(dire->name,sizeof(*block) - offset*sizeof(afs_dirent_t));
+
+               _debug("ENT[%u.%u]: %s %u \"%.*s\"\n",
+                      blkoff/sizeof(afs_dir_block_t),offset,
+                      offset<curr ? "skip" : "fill",
+                      nlen,nlen,dire->name);
+
+               /* work out where the next possible entry is */
+               for (tmp=nlen; tmp>15; tmp-=sizeof(afs_dirent_t)) {
+                       if (next>=AFS_DIRENT_PER_BLOCK) {
+                               _debug("ENT[%u.%u]:"
+                                      " %u travelled beyond end dir block (len %u/%u)\n",
+                                      blkoff/sizeof(afs_dir_block_t),offset,next,tmp,nlen);
+                               return -EIO;
+                       }
+                       if (!(block->pagehdr.bitmap[next/8] & (1 << (next % 8)))) {
+                               _debug("ENT[%u.%u]: %u unmarked extension (len %u/%u)\n",
+                                      blkoff/sizeof(afs_dir_block_t),offset,next,tmp,nlen);
+                               return -EIO;
+                       }
+
+                       _debug("ENT[%u.%u]: ext %u/%u\n",
+                              blkoff/sizeof(afs_dir_block_t),next,tmp,nlen);
+                       next++;
+               }
+
+               /* skip if starts before the current position */
+               if (offset<curr)
+                       continue;
+
+               /* found the next entry */
+               ret = filldir(cookie,
+                             dire->name,
+                             nlen,
+                             blkoff + offset * sizeof(afs_dirent_t),
+                             ntohl(dire->vnode),
+                             filldir==afs_dir_lookup_filldir ? dire->unique : DT_UNKNOWN);
+               if (ret<0) {
+                       _leave(" = 0 [full]");
+                       return 0;
+               }
+
+               *fpos = blkoff + next * sizeof(afs_dirent_t);
+       }
+
+       _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)
+{
+       afs_dir_block_t *dblock;
+       afs_dir_page_t *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(afs_dirent_t) - 1;
+       *fpos &= ~(sizeof(afs_dirent_t) - 1);
+
+       /* walk through the blocks in sequence */
+       ret = 0;
+       while (*fpos < dir->i_size) {
+               blkoff = *fpos & ~(sizeof(afs_dir_block_t) - 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(afs_dir_block_t)];
+                       ret = afs_dir_iterate_block(fpos,dblock,blkoff,cookie,filldir);
+                       if (ret!=1) {
+                               afs_dir_put_page(page);
+                               goto out;
+                       }
+
+                       blkoff += sizeof(afs_dir_block_t);
+
+               } 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,%u},%s,%u,,%lu,%u",cookie->name,cookie->nlen,name,nlen,ino,ntohl(dtype));
+
+       if (cookie->nlen != nlen || memcmp(cookie->name,name,nlen)!=0) {
+               _leave(" = 0 [no]");
+               return 0;
+       }
+
+       cookie->fid.vnode = ino;
+       cookie->fid.unique = ntohl(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 afs_dir_lookup_cookie cookie;
+       struct afs_super_info *as;
+       struct inode *inode;
+       afs_vnode_t *vnode;
+       unsigned fpos;
+       int ret;
+
+       _enter("{%lu},{%s}",dir->i_ino,dentry->d_name.name);
+
+       /* insanity checks first */
+       if (sizeof(afs_dir_block_t) != 2048) BUG();
+       if (sizeof(afs_dirent_t) != 32) BUG();
+
+       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) 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, int flags)
+{
+       struct afs_dir_lookup_cookie cookie;
+       struct dentry *parent;
+       struct inode *inode, *dir;
+       unsigned fpos;
+       int ret;
+
+       _enter("%s,%x",dentry->d_name.name,flags);
+
+       /* lock down the parent dentry so we can peer at it */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       read_lock(&dparent_lock);
+       parent = dget(dentry->d_parent);
+       read_unlock(&dparent_lock);
+#else
+       lock_kernel();
+       parent = dget(dentry->d_parent);
+       unlock_kernel();
+#endif
+
+       dir = parent->d_inode;
+       inode = dentry->d_inode;
+
+       /* handle a negative inode */
+       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)dentry->d_fsdata != (unsigned)AFS_FS_I(dir)->status.version) {
+               _debug("%s: parent changed %u -> %u",
+                      dentry->d_name.name,
+                      (unsigned)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_inode_pages(inode->i_mapping);
+                       goto out_bad;
+               }
+
+               dentry->d_fsdata = (void*) (unsigned) 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:
+       dentry->d_flags |= DCACHE_NFSFS_RENAMED;
+
+ 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 (file)
index 0000000..115befe
--- /dev/null
@@ -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 _H_DB712916_5113_11D6_9A6D_0002B3163499
+#define _H_DB712916_5113_11D6_9A6D_0002B3163499
+
+#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 /* _H_DB712916_5113_11D6_9A6D_0002B3163499 */
diff --git a/fs/afs/file.c b/fs/afs/file.c
new file mode 100644 (file)
index 0000000..d14e427
--- /dev/null
@@ -0,0 +1,143 @@
+/* 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 "volume.h"
+#include "vnode.h"
+#include <rxrpc/call.h>
+#include "internal.h"
+
+//static int afs_file_open(struct inode *inode, struct file *file);
+//static int afs_file_release(struct inode *inode, struct file *file);
+
+static int afs_file_readpage(struct file *file, struct page *page);
+
+//static ssize_t afs_file_read(struct file *file, char *buf, size_t size, loff_t *off);
+
+static ssize_t afs_file_write(struct file *file, const char *buf, size_t size, loff_t *off);
+
+struct inode_operations afs_file_inode_operations = {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       .getattr        = afs_inode_getattr,
+#else
+       .revalidate     = afs_inode_revalidate,
+#endif
+};
+
+struct file_operations afs_file_file_operations = {
+//     .open           = afs_file_open,
+//     .release        = afs_file_release,
+       .read           = generic_file_read, //afs_file_read,
+       .write          = afs_file_write,
+       .mmap           = generic_file_mmap,
+//     .fsync          = afs_file_fsync,
+};
+
+struct address_space_operations afs_fs_aops = {
+       .readpage       = afs_file_readpage,
+};
+
+/*****************************************************************************/
+/*
+ * AFS file read
+ */
+#if 0
+static ssize_t afs_file_read(struct file *file, char *buf, size_t size, loff_t *off)
+{
+       struct afs_inode_info *ai;
+
+       ai = AFS_FS_I(file->f_dentry->d_inode);
+       if (ai->flags & AFS_INODE_DELETED)
+               return -ESTALE;
+
+       return -EIO;
+} /* end afs_file_read() */
+#endif
+
+/*****************************************************************************/
+/*
+ * AFS file write
+ */
+static ssize_t afs_file_write(struct file *file, const char *buf, size_t size, loff_t *off)
+{
+       afs_vnode_t *vnode;
+
+       vnode = AFS_FS_I(file->f_dentry->d_inode);
+       if (vnode->flags & AFS_VNODE_DELETED)
+               return -ESTALE;
+
+       return -EIO;
+} /* end afs_file_write() */
+
+/*****************************************************************************/
+/*
+ * AFS read page from file (or symlink)
+ */
+static int afs_file_readpage(struct file *file, struct page *page)
+{
+       struct afs_rxfs_fetch_descriptor desc;
+       struct inode *inode;
+       afs_vnode_t *vnode;
+       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;
+
+       /* work out how much to get and from where */
+       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;
+               }
+               goto error;
+       }
+
+       SetPageUptodate(page);
+       unlock_page(page);
+
+       _leave(" = 0");
+       return 0;
+
+ error:
+       SetPageError(page);
+       unlock_page(page);
+
+       _leave(" = %d",ret);
+       return ret;
+
+} /* end afs_file_readpage() */
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
new file mode 100644 (file)
index 0000000..e4aabcb
--- /dev/null
@@ -0,0 +1,816 @@
+/* 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 server 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(afs_server_t *server, char *buf, size_t *buflen)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[2];
+       size_t sent;
+       int ret;
+       u32 param[1];
+
+       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(afs_server_t *server,
+                            const char *name,
+                            afs_volume_info_t *vinfo)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[3];
+       size_t sent;
+       int ret;
+       u32 param[2], *bp, zero;
+
+       _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(afs_server_t *server,
+                              afs_vnode_t *vnode,
+                              afs_volsync_t *volsync)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct afs_server_callslot callslot;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       size_t sent;
+       int ret;
+       u32 *bp;
+
+       _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(afs_server_t *server,
+                            afs_vnode_t *vnode,
+                            struct afs_rxfs_fetch_descriptor *desc,
+                            afs_volsync_t *volsync)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct afs_server_callslot callslot;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       size_t sent;
+       int ret;
+       u32 *bp;
+
+       _enter("%p,{fid={%u,%u,%u},sz=%u,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(afs_server_t *server, afs_vnode_t *vnode)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct afs_server_callslot callslot;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       size_t sent;
+       int ret;
+       u32 *bp;
+
+       _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(afs_server_t *server,
+                   afs_vnode_t *dir,
+                   const char *filename,
+                   afs_vnode_t *vnode,
+                   afs_volsync_t *volsync)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[3];
+       size_t sent;
+       int ret;
+       u32 *bp, zero;
+
+       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 (file)
index 0000000..0931a5b
--- /dev/null
@@ -0,0 +1,53 @@
+/* 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(afs_server_t *server,
+                                   const char *name,
+                                   afs_volume_info_t *vinfo);
+
+extern int afs_rxfs_fetch_file_status(afs_server_t *server,
+                                     afs_vnode_t *vnode,
+                                     afs_volsync_t *volsync);
+
+struct afs_rxfs_fetch_descriptor {
+       afs_fid_t       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(afs_server_t *server,
+                                   afs_vnode_t *vnode,
+                                   struct afs_rxfs_fetch_descriptor *desc,
+                                   afs_volsync_t *volsync);
+
+extern int afs_rxfs_give_up_callback(afs_server_t *server, afs_vnode_t *vnode);
+
+/* this doesn't appear to work in OpenAFS server */
+extern int afs_rxfs_lookup(afs_server_t *server,
+                          afs_vnode_t *dir,
+                          const char *filename,
+                          afs_vnode_t *vnode,
+                          afs_volsync_t *volsync);
+
+/* this is apparently mis-implemented in OpenAFS server */
+extern int afs_rxfs_get_root_volume(afs_server_t *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 (file)
index 0000000..235b7b0
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * 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 {
+       afs_fid_t               fid;
+       afs_volume_t            *volume;        /* volume on which resides */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+       afs_vnode_t             *new_vnode;     /* new vnode record */
+#endif
+};
+
+/*****************************************************************************/
+/*
+ * map the AFS file status to the inode member variables
+ */
+static int afs_inode_map_status(afs_vnode_t *vnode)
+{
+       struct inode *inode = AFS_VNODE_TO_I(vnode);
+
+       _debug("FS: ft=%d lk=%d sz=%u 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_rdev           = NODEV;
+       inode->i_size           = vnode->status.size;
+       inode->i_atime          = inode->i_mtime = inode->i_ctime = vnode->status.mtime_server;
+       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
+ */
+int afs_inode_fetch_status(struct inode *inode)
+{
+       afs_vnode_t *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
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static int afs_iget5_test(struct inode *inode, void *opaque)
+{
+       struct afs_iget_data *data = opaque;
+
+       /* only match inodes with the same version number */
+       return inode->i_ino==data->fid.vnode && inode->i_version==data->fid.unique;
+} /* end afs_iget5_test() */
+#endif
+
+/*****************************************************************************/
+/*
+ * iget5() inode initialiser
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static int afs_iget5_set(struct inode *inode, void *opaque)
+{
+       struct afs_iget_data *data = opaque;
+       afs_vnode_t *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() */
+#endif
+
+/*****************************************************************************/
+/*
+ * iget4() comparator
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+static int afs_iget4_test(struct inode *inode, ino_t ino, void *opaque)
+{
+       struct afs_iget_data *data = opaque;
+
+       /* only match inodes with the same version number */
+       return inode->i_ino==data->fid.vnode && inode->i_version==data->fid.unique;
+} /* end afs_iget4_test() */
+#endif
+
+/*****************************************************************************/
+/*
+ * read an inode (2.4 only)
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+void afs_read_inode2(struct inode *inode, void *opaque)
+{
+       struct afs_iget_data *data = opaque;
+       afs_vnode_t *vnode;
+       int ret;
+
+       _enter(",{{%u,%u,%u},%p}",data->fid.vid,data->fid.vnode,data->fid.unique,data->volume);
+
+       if (inode->u.generic_ip) BUG();
+
+       /* attach a pre-allocated vnode record */
+       inode->u.generic_ip = vnode = data->new_vnode;
+       data->new_vnode = NULL;
+
+       memset(vnode,0,sizeof(*vnode));
+       vnode->inode = 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);
+       vnode->flags |= AFS_VNODE_CHANGED;
+       vnode->volume = data->volume;
+       vnode->fid = data->fid;
+
+       /* ask the server for a status check */
+       ret = afs_vnode_fetch_status(vnode);
+       if (ret<0) {
+               make_bad_inode(inode);
+               _leave(" [bad inode]");
+               return;
+       }
+
+       ret = afs_inode_map_status(vnode);
+       if (ret<0) {
+               make_bad_inode(inode);
+               _leave(" [bad inode]");
+               return;
+       }
+
+       _leave("");
+       return;
+} /* end afs_read_inode2() */
+#endif
+
+/*****************************************************************************/
+/*
+ * inode retrieval
+ */
+inline int afs_iget(struct super_block *sb, afs_fid_t *fid, struct inode **_inode)
+{
+       struct afs_iget_data data = { fid: *fid };
+       struct afs_super_info *as;
+       struct inode *inode;
+       afs_vnode_t *vnode;
+       int ret;
+
+       _enter(",{%u,%u,%u},,",fid->vid,fid->vnode,fid->unique);
+
+       as = sb->s_fs_info;
+       data.volume = as->volume;
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       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;
+       }
+
+       /* okay... it's a new inode */
+       vnode->flags |= AFS_VNODE_CHANGED;
+       ret = afs_inode_fetch_status(inode);
+       if (ret<0)
+               goto bad_inode;
+
+#if 0
+       /* find a cache entry for it */
+       ret = afs_cache_lookup_vnode(as->volume,vnode);
+       if (ret<0)
+               goto bad_inode;
+#endif
+
+       /* success */
+       unlock_new_inode(inode);
+
+       *_inode = inode;
+       _leave(" = 0 [CB { v=%u x=%lu t=%u nix=%u }]",
+              vnode->cb_version,
+              vnode->cb_timeout.timo_jif,
+              vnode->cb_type,
+              vnode->nix
+              );
+       return 0;
+
+       /* failure */
+ bad_inode:
+       make_bad_inode(inode);
+       unlock_new_inode(inode);
+       iput(inode);
+
+       _leave(" = %d [bad]",ret);
+       return ret;
+
+#else
+
+       /* pre-allocate a vnode record so that afs_read_inode2() doesn't have to return an inode
+        * without one attached
+        */
+       data.new_vnode = kmalloc(sizeof(afs_vnode_t),GFP_KERNEL);
+       if (!data.new_vnode) {
+               _leave(" = -ENOMEM");
+               return -ENOMEM;
+       }
+
+       inode = iget4(sb,fid->vnode,afs_iget4_test,&data);
+       if (data.new_vnode) kfree(data.new_vnode); 
+       if (!inode) {
+               _leave(" = -ENOMEM");
+               return -ENOMEM;
+       }
+
+       vnode = AFS_FS_I(inode);
+       *_inode = inode;
+       _leave(" = 0 [CB { v=%u x=%lu t=%u nix=%u }]",
+              vnode->cb_version,
+              vnode->cb_timeout.timo_jif,
+              vnode->cb_type,
+              vnode->nix
+              );
+       return 0;
+#endif
+} /* end afs_iget() */
+
+/*****************************************************************************/
+/*
+ * read the attributes of an inode
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
+{
+       struct inode *inode;
+       afs_vnode_t *vnode;
+       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() */
+#endif
+
+/*****************************************************************************/
+/*
+ * revalidate the inode
+ */
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,0)
+int afs_inode_revalidate(struct dentry *dentry)
+{
+       struct inode *inode;
+       afs_vnode_t *vnode;
+       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;
+       }
+
+       _leave(" = 0 CB { v=%u x=%u t=%u }",
+              vnode->cb_version,
+              vnode->cb_expiry,
+              vnode->cb_type);
+
+       return 0;
+} /* end afs_inode_revalidate() */
+#endif
+
+/*****************************************************************************/
+/*
+ * clear an AFS inode
+ */
+void afs_clear_inode(struct inode *inode)
+{
+       afs_vnode_t *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
+              );
+
+       if (inode->i_ino!=vnode->fid.vnode) BUG();
+
+       afs_vnode_give_up_callback(vnode);
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,0)
+       if (inode->u.generic_ip) kfree(inode->u.generic_ip);
+#endif
+
+       _leave("");
+} /* end afs_clear_inode() */
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
new file mode 100644 (file)
index 0000000..37f84bb
--- /dev/null
@@ -0,0 +1,127 @@
+/* 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/version.h>
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+
+/*
+ * debug tracing
+ */
+#define kenter(FMT,...)        printk("==> %s("FMT")\n",__FUNCTION__,##__VA_ARGS__)
+#define kleave(FMT,...)        printk("<== %s()"FMT"\n",__FUNCTION__,##__VA_ARGS__)
+#define kdebug(FMT,...)        printk(FMT"\n",##__VA_ARGS__)
+#define kproto(FMT,...)        printk("### "FMT"\n",##__VA_ARGS__)
+#define knet(FMT,...)  printk(FMT"\n",##__VA_ARGS__)
+
+#if 0
+#define _enter(FMT,...)        kenter(FMT,##__VA_ARGS__)
+#define _leave(FMT,...)        kleave(FMT,##__VA_ARGS__)
+#define _debug(FMT,...)        kdebug(FMT,##__VA_ARGS__)
+#define _proto(FMT,...)        kproto(FMT,##__VA_ARGS__)
+#define _net(FMT,...)  knet(FMT,##__VA_ARGS__)
+#else
+#define _enter(FMT,...)        do { } while(0)
+#define _leave(FMT,...)        do { } while(0)
+#define _debug(FMT,...)        do { } while(0)
+#define _proto(FMT,...)        do { } while(0)
+#define _net(FMT,...)  do { } while(0)
+#endif
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,5,0)
+#define wait_on_page_locked wait_on_page
+#define PageUptodate Page_Uptodate
+
+static inline struct proc_dir_entry *PDE(const struct inode *inode)
+{
+       return (struct proc_dir_entry *)inode->u.generic_ip;
+}
+#endif
+
+static inline void afs_discard_my_signals(void)
+{
+       while (signal_pending(current)) {
+               siginfo_t sinfo;
+
+               spin_lock_irq(&current->sig->siglock);
+               dequeue_signal(&current->blocked,&sinfo);
+               spin_unlock_irq(&current->sig->siglock);
+       }
+}
+
+/*
+ * cell.c
+ */
+extern struct rw_semaphore afs_proc_cells_sem;
+extern struct list_head afs_proc_cells;
+
+/*
+ * 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;
+
+/*
+ * inode.c
+ */
+extern int afs_iget(struct super_block *sb, afs_fid_t *fid, struct inode **_inode);
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+extern int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat);
+#else
+extern void afs_read_inode2(struct inode *inode, void *opaque);
+extern int afs_inode_revalidate(struct dentry *dentry);
+#endif
+extern void afs_clear_inode(struct inode *inode);
+
+/*
+ * mntpt.c
+ */
+extern struct inode_operations afs_mntpt_inode_operations;
+extern struct file_operations afs_mntpt_file_operations;
+
+extern int afs_mntpt_check_symlink(afs_vnode_t *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)(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(afs_cell_t *cell);
+extern void afs_proc_cell_remove(afs_cell_t *cell);
+
+#endif /* AFS_INTERNAL_H */
diff --git a/fs/afs/kafsasyncd.c b/fs/afs/kafsasyncd.c
new file mode 100644 (file)
index 0000000..2891e98
--- /dev/null
@@ -0,0 +1,260 @@
+/* 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/version.h>
+#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 spinlock_t kafsasyncd_async_lock = SPIN_LOCK_UNLOCKED;
+
+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)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct list_head *_p;
+       int die;
+
+       kafsasyncd_task = current;
+
+       printk("kAFS: Started kafsasyncd %d\n",current->pid);
+       strcpy(current->comm,"kafsasyncd");
+
+       daemonize();
+
+       complete(&kafsasyncd_alive);
+
+       /* only certain signals are of interest */
+       spin_lock_irq(&current->sig->siglock);
+       siginitsetinv(&current->blocked,0);
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,3)
+       recalc_sigpending();
+#else
+       recalc_sigpending(current);
+#endif
+       spin_unlock_irq(&current->sig->siglock);
+
+       /* 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);
+
+               /* 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,afs_async_op_t,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(&kafsasyncd_async_busyq,&kafsasyncd_async_attnq);
+       list_del_init(&kafsasyncd_async_busyq);
+
+       /* dequeue kafsasyncd from all their wait queues */
+       list_for_each(_p,&kafsasyncd_async_attnq) {
+               afs_async_op_t *op = list_entry(_p,afs_async_op_t,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)) {
+               afs_async_op_t *op = list_entry(_p,afs_async_op_t,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(afs_async_op_t *op)
+{
+       _enter("");
+
+       spin_lock(&kafsasyncd_async_lock);
+
+       init_waitqueue_entry(&op->waiter,kafsasyncd_task);
+
+       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(afs_async_op_t *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(afs_async_op_t *op)
+{
+       _enter("");
+
+       spin_lock(&kafsasyncd_async_lock);
+
+       list_del_init(&op->link);
+
+       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 (file)
index 0000000..6438c17
--- /dev/null
@@ -0,0 +1,49 @@
+/* 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_ops {
+       void (*attend)(afs_async_op_t *op);
+       void (*discard)(afs_async_op_t *op);
+};
+
+/*****************************************************************************/
+/*
+ * asynchronous operation record
+ */
+struct afs_async_op
+{
+       struct list_head                link;
+       afs_server_t                    *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(afs_async_op_t *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(afs_async_op_t *op);
+extern void afs_kafsasyncd_attend_op(afs_async_op_t *op);
+extern void afs_kafsasyncd_terminate_op(afs_async_op_t *op);
+
+#endif /* _LINUX_AFS_KAFSASYNCD_H */
diff --git a/fs/afs/kafstimod.c b/fs/afs/kafstimod.c
new file mode 100644 (file)
index 0000000..ccc1b4e
--- /dev/null
@@ -0,0 +1,211 @@
+/* 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/version.h>
+#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 spinlock_t kafstimod_lock = SPIN_LOCK_UNLOCKED;
+
+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)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       afs_timer_t *timer;
+
+       printk("kAFS: Started kafstimod %d\n",current->pid);
+       strcpy(current->comm,"kafstimod");
+
+       daemonize();
+
+       complete(&kafstimod_alive);
+
+       /* only certain signals are of interest */
+       spin_lock_irq(&current->sig->siglock);
+       siginitsetinv(&current->blocked,0);
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,3)
+       recalc_sigpending();
+#else
+       recalc_sigpending(current);
+#endif
+       spin_unlock_irq(&current->sig->siglock);
+
+       /* 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);
+               }
+
+               /* 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,afs_timer_t,link);
+                       timeout = timer->timo_jif;
+                       jif = jiffies;
+
+                       if (time_before_eq(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(afs_timer_t *timer, unsigned long timeout)
+{
+       struct list_head *_p;
+       afs_timer_t *ptimer;
+
+       _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,afs_timer_t,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_queue_vlocation() */
+
+/*****************************************************************************/
+/*
+ * dequeue a timer
+ * - returns 0 if the timer was deleted or -ENOENT if it wasn't queued
+ */
+int afs_kafstimod_del_timer(afs_timer_t *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 (file)
index 0000000..342d81d
--- /dev/null
@@ -0,0 +1,45 @@
+/* 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_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(afs_timer_t *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(afs_timer_t *timer, unsigned long timeout);
+extern int afs_kafstimod_del_timer(afs_timer_t *timer);
+
+#endif /* _LINUX_AFS_KAFSTIMOD_H */
diff --git a/fs/afs/main.c b/fs/afs/main.c
new file mode 100644 (file)
index 0000000..dc20f67
--- /dev/null
@@ -0,0 +1,193 @@
+/* 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/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 "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_init(void);
+static void afs_exit(void);
+static int afs_adding_peer(struct rxrpc_peer *peer);
+static void afs_discarding_peer(struct rxrpc_peer *peer);
+
+module_init(afs_init);
+module_exit(afs_exit);
+
+MODULE_DESCRIPTION("AFS Client File System");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+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];
+spinlock_t afs_cb_hash_lock = SPIN_LOCK_UNLOCKED;
+
+/*****************************************************************************/
+/*
+ * initialise the AFS client FS module
+ */
+static int 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;
+
+       /* initialise the cell DB */
+       ret = afs_cell_init();
+       if (ret<0)
+               goto error;
+
+       /* start the timeout daemon */
+       ret = afs_kafstimod_start();
+       if (ret<0)
+               goto error;
+
+       /* 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:
+       afs_cell_purge();
+       afs_proc_cleanup();
+       printk(KERN_ERR "kAFS: failed to register: %d\n",ret);
+       return ret;
+} /* end afs_init() */
+
+/*****************************************************************************/
+/*
+ * clean up on module removal
+ */
+static void 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();
+       afs_proc_cleanup();
+
+} /* end 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)
+{
+       afs_server_t *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)
+{
+       afs_server_t *server;
+
+       _enter("%p",peer);
+
+       _debug("Discarding peer %08x (rtt=%lu.%lumS)\n",
+              ntohl(peer->addr.s_addr),
+              peer->rtt/1000,
+              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;
+
+               //_debug("Server %p{u=%d}\n",server,atomic_read(&server->usage));
+               //_debug("Cell %p{u=%d}\n",server->cell,atomic_read(&server->cell->usage));
+       }
+       spin_unlock(&afs_server_peer_lock);
+
+       _leave("");
+
+} /* end afs_discarding_peer() */
diff --git a/fs/afs/misc.c b/fs/afs/misc.c
new file mode 100644 (file)
index 0000000..e4fce66
--- /dev/null
@@ -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 (file)
index 0000000..4e88180
--- /dev/null
@@ -0,0 +1,112 @@
+/* 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 "volume.h"
+#include "vnode.h"
+#include "internal.h"
+
+
+static struct dentry *afs_mntpt_lookup(struct inode *dir, struct dentry *dentry);
+static int afs_mntpt_open(struct inode *inode, struct file *file);
+
+struct file_operations afs_mntpt_file_operations = {
+       .open           = afs_mntpt_open,
+};
+
+struct inode_operations afs_mntpt_inode_operations = {
+       .lookup         = afs_mntpt_lookup,
+       .readlink       = page_readlink,
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       .getattr        = afs_inode_getattr,
+#else
+       .revalidate     = afs_inode_revalidate,
+#endif
+};
+
+/*****************************************************************************/
+/*
+ * 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(afs_vnode_t *vnode)
+{
+       struct page *page;
+       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 */
+       page = read_cache_page(AFS_VNODE_TO_I(vnode)->i_mapping,0,
+                              (filler_t*)AFS_VNODE_TO_I(vnode)->i_mapping->a_ops->readpage,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,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)
+{
+       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)
+{
+       return -EREMOTE;
+} /* end afs_mntpt_open() */
diff --git a/fs/afs/mount.h b/fs/afs/mount.h
new file mode 100644 (file)
index 0000000..fbdd778
--- /dev/null
@@ -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 */
+       u_int32_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 (file)
index 0000000..83fda6d
--- /dev/null
@@ -0,0 +1,739 @@
+/* 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 *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_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;
+
+       _leave(" = 0");
+       return 0;
+
+#if 0
+ error_cells:
+       remove_proc_entry("cells",proc_afs);
+#endif
+ 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)
+{
+       afs_cell_t *cell = list_entry(v,afs_cell_t,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 *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) {
+               afs_cell_t *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() */
+
+/*****************************************************************************/
+/*
+ * initialise /proc/fs/afs/<cell>/
+ */
+int afs_proc_cell_setup(afs_cell_t *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(afs_cell_t *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 seq_file *m;
+       afs_cell_t *cell;
+       int ret;
+
+       cell = afs_get_cell_maybe((afs_cell_t**)&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)
+{
+       afs_cell_t *cell = PDE(inode)->data;
+       int ret;
+
+       ret = seq_release(inode,file);
+
+       afs_put_cell(cell);
+
+} /* 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;
+       afs_cell_t *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;
+       afs_cell_t *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)
+{
+       afs_cell_t *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)
+{
+       afs_vlocation_t *vlocation = list_entry(v,afs_vlocation_t,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 seq_file *m;
+       afs_cell_t *cell;
+       int ret;
+
+       cell = afs_get_cell_maybe((afs_cell_t**)&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)
+{
+       afs_cell_t *cell = PDE(inode)->data;
+       int ret;
+
+       ret = seq_release(inode,file);
+
+       afs_put_cell(cell);
+
+} /* 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)
+{
+       afs_cell_t *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)
+{
+       afs_cell_t *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)
+{
+       afs_cell_t *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 seq_file *m;
+       afs_cell_t *cell;
+       int ret;
+
+       cell = afs_get_cell_maybe((afs_cell_t**)&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)
+{
+       afs_cell_t *cell = PDE(inode)->data;
+       int ret;
+
+       ret = seq_release(inode,file);
+
+       afs_put_cell(cell);
+
+} /* 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;
+       afs_cell_t *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;
+       afs_cell_t *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)
+{
+       afs_cell_t *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)
+{
+       afs_server_t *server = list_entry(v,afs_server_t,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 (file)
index 0000000..b249d7c
--- /dev/null
@@ -0,0 +1,489 @@
+/* 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"
+
+spinlock_t afs_server_peer_lock = SPIN_LOCK_UNLOCKED;
+
+#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(afs_timer_t *timer)
+{
+       afs_server_t *server = list_entry(timer,afs_server_t,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(afs_cell_t *cell, const struct in_addr *addr, afs_server_t **_server)
+{
+       struct list_head *_p;
+       afs_server_t *server, *active, *zombie;
+       int loop;
+
+       _enter("%p,%08x,",cell,ntohl(addr->s_addr));
+
+       /* allocate and initialise a server record */
+       server = kmalloc(sizeof(afs_server_t),GFP_KERNEL);
+       if (!server) {
+               _leave(" = -ENOMEM");
+               return -ENOMEM;
+       }
+
+       memset(server,0,sizeof(afs_server_t));
+       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(_p,&cell->sv_list) {
+               active = list_entry(_p,afs_server_t,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(_p,&cell->sv_graveyard) {
+               zombie = list_entry(_p,afs_server_t,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 rec */
+ 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(afs_server_t *server)
+{
+       afs_cell_t *cell;
+
+       _enter("%p",server);
+
+       cell = server->cell;
+
+       /* sanity check */
+       if (atomic_read(&server->usage)<=0)
+               BUG();
+
+       /* 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(afs_server_t *server)
+{
+       struct rxrpc_peer *peer;
+       afs_cell_t *cell;
+       int loop;
+
+       _enter("%p",server);
+
+       cell = server->cell;
+
+       if (atomic_read(&server->usage)<0) BUG();
+
+       /* 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(afs_server_t *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);
+       }
+
+       if (callslot->conn) 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(afs_server_t *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);
+
+       if (callslot->nconn<0) BUG();
+
+       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);
+       }
+
+       if (callslot->conn) 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(afs_server_t *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 (file)
index 0000000..feddacf
--- /dev/null
@@ -0,0 +1,97 @@
+/* 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;
+       afs_cell_t              *cell;          /* cell in which server resides */
+       struct list_head        link;           /* link in cell's server list */
+       struct rw_semaphore     sem;            /* access lock */
+       afs_timer_t             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(afs_cell_t *cell, const struct in_addr *addr, afs_server_t **_server);
+
+#define afs_get_server(S) do { atomic_inc(&(S)->usage); } while(0)
+
+extern void afs_put_server(afs_server_t *server);
+extern void afs_server_do_timeout(afs_server_t *server);
+
+extern int afs_server_find_by_peer(const struct rxrpc_peer *peer, afs_server_t **_server);
+
+extern int afs_server_get_vlconn(afs_server_t *server, struct rxrpc_connection **_conn);
+
+static inline afs_server_t *afs_server_get_from_peer(struct rxrpc_peer *peer)
+{
+       afs_server_t *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(afs_server_t *server,
+                                      struct afs_server_callslot *callslot);
+
+extern void afs_server_release_callslot(afs_server_t *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 (file)
index 0000000..1805653
--- /dev/null
@@ -0,0 +1,595 @@
+/*
+ * 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' */
+
+static inline char *strdup(const char *s)
+{
+       char *ns = kmalloc(strlen(s)+1,GFP_KERNEL);
+       if (ns)
+               strcpy(ns,s);
+       return ns;
+}
+
+static void afs_i_init_once(void *foo, kmem_cache_t *cachep, unsigned long flags);
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static struct super_block *afs_get_sb(struct file_system_type *fs_type,
+                                     int flags, char *dev_name, void *data);
+#else
+static struct super_block *afs_read_super(struct super_block *sb, void *data, int);
+#endif
+
+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",
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       .get_sb         = afs_get_sb,
+       .kill_sb        = kill_anon_super,
+#else
+       .read_super     = afs_read_super,
+#endif
+};
+
+static struct super_operations afs_super_ops = {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       .statfs         = simple_statfs,
+       .alloc_inode    = afs_alloc_inode,
+       .drop_inode     = generic_delete_inode,
+       .destroy_inode  = afs_destroy_inode,
+#else
+       .read_inode2    = afs_read_inode2,
+#endif
+       .clear_inode    = afs_clear_inode,
+       .put_super      = afs_put_super,
+};
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static kmem_cache_t *afs_inode_cachep;
+#endif
+
+/*****************************************************************************/
+/*
+ * initialise the filesystem
+ */
+int __init afs_fs_init(void)
+{
+       int ret;
+
+       kenter("");
+
+       /* open the cache */
+#if 0
+       ret = -EINVAL;
+       if (!cachedev) {
+               printk(KERN_NOTICE "kAFS: No cache device specified as module parm\n");
+               printk(KERN_NOTICE "kAFS: Set with \"cachedev=<devname>\" on insmod's cmdline\n");
+               return ret;
+       }
+
+       ret = afs_cache_open(cachedev,&afs_cache);
+       if (ret<0) {
+               printk(KERN_NOTICE "kAFS: Failed to open cache device\n");
+               return ret;
+       }
+#endif
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       /* create ourselves an inode cache */
+       ret = -ENOMEM;
+       afs_inode_cachep = kmem_cache_create("afs_inode_cache",
+                                               sizeof(afs_vnode_t),
+                                               0,
+                                               SLAB_HWCACHE_ALIGN,
+                                               afs_i_init_once,
+                                               NULL);
+       if (!afs_inode_cachep) {
+               printk(KERN_NOTICE "kAFS: Failed to allocate inode cache\n");
+#if 0
+               afs_put_cache(afs_cache);
+#endif
+               return ret;
+       }
+#endif
+
+       /* now export our filesystem to lesser mortals */
+       ret = register_filesystem(&afs_fs_type);
+       if (ret<0) {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+               kmem_cache_destroy(afs_inode_cachep);
+#endif
+#if 0
+               afs_put_cache(afs_cache);
+#endif
+               kleave(" = %d",ret);
+               return ret;
+       }
+
+       kleave(" = 0");
+       return 0;
+} /* end afs_fs_init() */
+
+/*****************************************************************************/
+/*
+ * clean up the filesystem
+ */
+void __exit afs_fs_exit(void)
+{
+       /* destroy our private inode cache */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       kmem_cache_destroy(afs_inode_cachep);
+#endif
+
+       unregister_filesystem(&afs_fs_type);
+
+#if 0
+       if (afs_cache)
+               afs_put_cache(afs_cache);
+#endif
+} /* 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 is a value
+ */
+#if 0
+static int want_value(char **_value, const char *option)
+{
+       if (!_value || !*_value || !**_value) {
+               printk(KERN_NOTICE "kAFS: %s: argument incomplete\n",option);
+               return 0;
+       }
+       return 1;
+} /* end want_value() */
+#endif
+
+/*****************************************************************************/
+/*
+ * 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() */
+
+/*****************************************************************************/
+/*
+ * extract a number from an option string value
+ */
+#if 0
+static int want_number(char **_value, const char *option, unsigned long *number,
+                      unsigned long limit)
+{
+       char *value = *_value;
+
+       if (!want_value(_value,option))
+               return 0;
+
+       *number = simple_strtoul(value,_value,0);
+
+       if (value==*_value) {
+               printk(KERN_NOTICE "kAFS: %s: Invalid number: %s\n",option,value);
+               return 0;
+       }
+
+       if (*number>limit) {
+               printk(KERN_NOTICE "kAFS: %s: numeric value %lu > %lu\n",option,*number,limit);
+               return 0;
+       }
+
+       return 1;
+} /* end want_number() */
+#endif
+
+/*****************************************************************************/
+/*
+ * extract a separator from an option string value
+ */
+#if 0
+static int want_sep(char **_value, const char *option, char sep)
+{
+       if (!want_value(_value,option))
+               return 0;
+
+       if (*(*_value)++ != sep) {
+               printk(KERN_NOTICE "kAFS: %s: '%c' expected: %s\n",option,sep,*_value-1);
+               return 0;
+       }
+
+       return 1;
+} /* end want_number() */
+#endif
+
+/*****************************************************************************/
+/*
+ * extract an IP address from an option string value
+ */
+#if 0
+static int want_ipaddr(char **_value, const char *option, struct in_addr *addr)
+{
+       unsigned long number[4];
+
+       if (!want_value(_value,option))
+               return 0;
+
+       if (!want_number(_value,option,&number[0],255) ||
+           !want_sep(_value,option,'.') ||
+           !want_number(_value,option,&number[1],255) ||
+           !want_sep(_value,option,'.') ||
+           !want_number(_value,option,&number[2],255) ||
+           !want_sep(_value,option,'.') ||
+           !want_number(_value,option,&number[3],255))
+               return 0;
+
+       ((u8*)addr)[0] = number[0];
+       ((u8*)addr)[1] = number[1];
+       ((u8*)addr)[2] = number[2];
+       ((u8*)addr)[3] = number[3];
+
+       return 1;
+} /* end want_numeric() */
+#endif
+
+/*****************************************************************************/
+/*
+ * 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_super_info *as, char *options, char **devname)
+{
+       char *key, *value;
+       int ret;
+
+       _enter("%s",options);
+
+       ret = 0;
+       while ((key = strsep(&options,",")))
+       {
+               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;
+                       as->rwparent = 1;
+                       continue;
+               }
+               else if (strcmp(key,"vol")==0) {
+                       if (!want_arg(&value,"vol")) return -EINVAL;
+                       *devname = value;
+                       continue;
+               }
+
+#if 0
+               if (strcmp(key,"servers")==0) {
+                       if (!want_arg(&value,"servers")) return -EINVAL;
+
+                       _debug("servers=%s",value);
+
+                       for (;;) {
+                               struct in_addr addr;
+
+                               if (!want_ipaddr(&value,"servers",&addr))
+                                       return -EINVAL;
+
+                               ret = afs_create_server(as->cell,&addr,&as->server);
+                               if (ret<0) {
+                                       printk("kAFS: unable to create server: %d\n",ret);
+                                       return ret;
+                               }
+
+                               if (!*value)
+                                       break;
+
+                               if (as->server) {
+                                       printk(KERN_NOTICE
+                                              "kAFS: only one server can be specified\n");
+                                       return -EINVAL;
+                               }
+
+                               if (!want_sep(&value,"servers",':'))
+                                       return -EINVAL;
+                       }
+                       continue;
+               }
+#endif
+
+               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() */
+
+/*****************************************************************************/
+/*
+ * fill in the superblock
+ */
+static int afs_fill_super(struct super_block *sb, void *_data, int silent)
+{
+       struct afs_super_info *as = NULL;
+       struct dentry *root = NULL;
+       struct inode *inode = NULL;
+       afs_fid_t fid;
+       void **data = _data;
+       char *options, *devname;
+       int ret;
+
+       _enter("");
+
+       if (!data) {
+               _leave(" = -EINVAL");
+               return -EINVAL;
+       }
+       devname = data[0];
+       options = data[1];
+       if (options)
+               options[PAGE_SIZE-1] = 0;
+
+       /* 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));
+
+       /* parse the options */
+       if (options) {
+               ret = afs_super_parse_options(as,options,&devname);
+               if (ret<0)
+                       goto error;
+               if (!devname) {
+                       printk("kAFS: no volume name specified\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+       }
+
+       /* parse the device name */
+       ret = afs_volume_lookup(devname,as->rwparent,&as->volume);
+       if (ret<0)
+               goto error;
+
+       /* 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;
+
+       _leave(" = 0");
+       return 0;
+
+ error:
+       if (root) dput(root);
+       if (inode) iput(inode);
+       if (as) {
+               if (as->volume)         afs_put_volume(as->volume);
+               kfree(as);
+       }
+       sb->s_fs_info = NULL;
+
+       _leave(" = %d",ret);
+       return ret;
+} /* end afs_fill_super() */
+
+/*****************************************************************************/
+/*
+ * get an AFS superblock
+ * - TODO: don't use get_sb_nodev(), but rather call sget() directly
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static struct super_block *afs_get_sb(struct file_system_type *fs_type,
+                                     int flags,
+                                     char *dev_name,
+                                     void *options)
+{
+       struct super_block *sb;
+       void *data[2] = { dev_name, options };
+       int ret;
+
+       _enter(",,%s,%p",dev_name,options);
+
+       /* start the cache manager */
+       ret = afscm_start();
+       if (ret<0) {
+               _leave(" = %d",ret);
+               return ERR_PTR(ret);
+       }
+
+       /* allocate a deviceless superblock */
+       sb = get_sb_nodev(fs_type,flags,data,afs_fill_super);
+       if (IS_ERR(sb)) {
+               afscm_stop();
+               return sb;
+       }
+
+       _leave("");
+       return sb;
+} /* end afs_get_sb() */
+#endif
+
+/*****************************************************************************/
+/*
+ * read an AFS superblock
+ */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+static struct super_block *afs_read_super(struct super_block *sb, void *options, int silent)
+{
+       void *data[2] = { NULL, options };
+       int ret;
+
+       _enter(",,%s",(char*)options);
+
+       /* start the cache manager */
+       ret = afscm_start();
+       if (ret<0) {
+               _leave(" = NULL (%d)",ret);
+               return NULL;
+       }
+
+       /* allocate a deviceless superblock */
+       ret = afs_fill_super(sb,data,silent);
+       if (ret<0) {
+               afscm_stop();
+               _leave(" = NULL (%d)",ret);
+               return NULL;
+       }
+
+       _leave(" = %p",sb);
+       return sb;
+} /* end afs_read_super() */
+#endif
+
+/*****************************************************************************/
+/*
+ * 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("");
+
+       if (as) {
+               if (as->volume)         afs_put_volume(as->volume);
+       }
+
+       /* stop the cache manager */
+       afscm_stop();
+
+       _leave("");
+} /* end afs_put_super() */
+
+/*****************************************************************************/
+/*
+ * initialise an inode cache slab element prior to any use
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static void afs_i_init_once(void *_vnode, kmem_cache_t *cachep, unsigned long flags)
+{
+       afs_vnode_t *vnode = (afs_vnode_t *) _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() */
+#endif
+
+/*****************************************************************************/
+/*
+ * allocate an AFS inode struct from our slab cache
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static struct inode *afs_alloc_inode(struct super_block *sb)
+{
+       afs_vnode_t *vnode;
+
+       vnode = (afs_vnode_t *) kmem_cache_alloc(afs_inode_cachep,SLAB_KERNEL);
+       if (!vnode)
+               return NULL;
+
+       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() */
+#endif
+
+/*****************************************************************************/
+/*
+ * destroy an AFS inode struct
+ */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+static void afs_destroy_inode(struct inode *inode)
+{
+       _enter("{%lu}",inode->i_ino);
+       kmem_cache_free(afs_inode_cachep, AFS_FS_I(inode));
+} /* end afs_destroy_inode() */
+#endif
diff --git a/fs/afs/super.h b/fs/afs/super.h
new file mode 100644 (file)
index 0000000..b307b08
--- /dev/null
@@ -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
+{
+       afs_volume_t            *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 (file)
index 0000000..7013ae6
--- /dev/null
@@ -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 (file)
index 0000000..411925f
--- /dev/null
@@ -0,0 +1,152 @@
+/* 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 struct afs_async_op            afs_async_op_t;
+typedef struct afs_callback            afs_callback_t;
+typedef struct afs_cell                        afs_cell_t;
+typedef struct afs_fid                 afs_fid_t;
+typedef struct afs_file_status         afs_file_status_t;
+typedef struct afs_server              afs_server_t;
+typedef struct afs_timer               afs_timer_t;
+typedef struct afs_vlocation           afs_vlocation_t;
+typedef struct afs_vnode               afs_vnode_t;
+typedef struct afs_volsync             afs_volsync_t;
+typedef struct afs_volume              afs_volume_t;
+typedef struct afs_volume_info         afs_volume_info_t;
+
+typedef struct afsc_cache              afsc_cache_t;
+typedef struct afsc_cache_cell         afsc_cache_cell_t;
+typedef struct afsc_cache_vldb         afsc_cache_vldb_t;
+typedef struct afsc_cell_record                afsc_cell_record_t;
+typedef struct afsc_inode              afsc_inode_t;
+typedef struct afsc_io                 afsc_io_t;
+typedef struct afsc_io_subop           afsc_io_subop_t;
+typedef struct afsc_io_queue           afsc_io_queue_t;
+typedef struct afsc_super_block                afsc_super_block_t;
+typedef struct afsc_vldb_record                afsc_vldb_record_t;
+typedef struct afsc_vnode_catalogue    afsc_vnode_catalogue_t;
+typedef struct afsc_vnode_meta         afsc_vnode_meta_t;
+
+typedef struct afsvl_dbentry           afsvl_dbentry_t;
+
+typedef enum {
+       AFSVL_RWVOL,                    /* read/write volume */
+       AFSVL_ROVOL,                    /* read-only volume */
+       AFSVL_BACKVOL,                  /* backup volume */
+} afs_voltype_t;
+
+extern const char *afs_voltypes[];
+
+typedef enum {
+       AFS_FTYPE_INVALID       = 0,
+       AFS_FTYPE_FILE          = 1,
+       AFS_FTYPE_DIR           = 2,
+       AFS_FTYPE_SYMLINK       = 3,
+} afs_file_type_t;
+
+#ifdef __KERNEL__
+
+/*****************************************************************************/
+/*
+ * 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
+{
+       afs_server_t            *server;        /* server that made the promise */
+       afs_fid_t               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 */
+       afs_fid_t               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 (file)
index 0000000..564e993
--- /dev/null
@@ -0,0 +1,662 @@
+/* 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() */
+
+/*****************************************************************************/
+/*
+ * probe a volume location server to see if it is still alive
+ */
+int afs_rxvl_probe(afs_server_t *server, int alloc_flags)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       size_t sent;
+       int ret;
+       u32 param[1];
+
+       /* 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() */
+
+/*****************************************************************************/
+/*
+ * look up a volume location database entry by name
+ */
+int afs_rxvl_get_entry_by_name(afs_server_t *server, const char *volname,
+                              afsc_vldb_record_t *entry)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[3];
+       unsigned tmp;
+       size_t sent;
+       int ret, loop;
+       u32 *bp, param[2], zero;
+
+       _enter(",%s,",volname);
+
+       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 = strlen(volname);
+       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] |= AFSC_VOL_STM_RW;
+               if (tmp & AFS_VLSF_ROVOL  ) entry->srvtmask[loop] |= AFSC_VOL_STM_RO;
+               if (tmp & AFS_VLSF_BACKVOL) entry->srvtmask[loop] |= AFSC_VOL_STM_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 |= AFSC_VOL_STM_RW;
+       if (tmp & AFS_VLF_ROEXISTS  ) entry->vidmask |= AFSC_VOL_STM_RO;
+       if (tmp & AFS_VLF_BACKEXISTS) entry->vidmask |= AFSC_VOL_STM_BAK;
+
+       ret = -ENOMEDIUM;
+       if (!entry->vidmask)
+               goto abort;
+
+       /* success */
+       entry->ctime = xtime.tv_sec;
+       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(afs_server_t *server,
+                            afs_volid_t volid,
+                            afs_voltype_t voltype,
+                            afsc_vldb_record_t *entry)
+{
+       DECLARE_WAITQUEUE(myself,current);
+
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       unsigned tmp;
+       size_t sent;
+       int ret, loop;
+       u32 *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] |= AFSC_VOL_STM_RW;
+               if (tmp & AFS_VLSF_ROVOL  ) entry->srvtmask[loop] |= AFSC_VOL_STM_RO;
+               if (tmp & AFS_VLSF_BACKVOL) entry->srvtmask[loop] |= AFSC_VOL_STM_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 |= AFSC_VOL_STM_RW;
+       if (tmp & AFS_VLF_ROEXISTS  ) entry->vidmask |= AFSC_VOL_STM_RO;
+       if (tmp & AFS_VLF_BACKEXISTS) entry->vidmask |= AFSC_VOL_STM_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] = AFSC_VOL_STM_RO;
+       entry->srvtmask[1] = AFSC_VOL_STM_RO;
+       entry->srvtmask[2] = AFSC_VOL_STM_RO | AFSC_VOL_STM_RW;
+#endif
+
+       /* success */
+       entry->ctime = xtime.tv_sec;
+       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(afs_async_op_t *op,
+                                  afs_volid_t volid,
+                                  afs_voltype_t voltype)
+{
+       struct rxrpc_connection *conn;
+       struct rxrpc_call *call;
+       struct iovec piov[1];
+       size_t sent;
+       int ret;
+       u32 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); /* force kafsasyncd to 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(afs_async_op_t *op,
+                                   afsc_vldb_record_t *entry)
+{
+       unsigned *bp, 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] |= AFSC_VOL_STM_RW;
+                       if (tmp & AFS_VLSF_ROVOL  ) entry->srvtmask[loop] |= AFSC_VOL_STM_RO;
+                       if (tmp & AFS_VLSF_BACKVOL) entry->srvtmask[loop] |= AFSC_VOL_STM_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 |= AFSC_VOL_STM_RW;
+               if (tmp & AFS_VLF_ROEXISTS  ) entry->vidmask |= AFSC_VOL_STM_RO;
+               if (tmp & AFS_VLF_BACKEXISTS) entry->vidmask |= AFSC_VOL_STM_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] = AFSC_VOL_STM_RO;
+               entry->srvtmask[1] = AFSC_VOL_STM_RO;
+               entry->srvtmask[2] = AFSC_VOL_STM_RO | AFSC_VOL_STM_RW;
+#endif
+
+               /* success */
+               entry->ctime = xtime.tv_sec;
+               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)
+{
+       afs_async_op_t *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=%d rdy=%u 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)
+{
+       afs_async_op_t *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 (file)
index 0000000..5791e04
--- /dev/null
@@ -0,0 +1,95 @@
+/* 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  afsvl_dbentry {
+       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];
+
+};
+
+/* probe a volume location server to see if it is still alive */
+extern int afs_rxvl_probe(afs_server_t *server, int alloc_flags);
+
+/* look up a volume location database entry by name */
+extern int afs_rxvl_get_entry_by_name(afs_server_t *server,
+                                     const char *volname,
+                                     afsc_vldb_record_t *entry);
+
+/* look up a volume location database entry by ID */
+extern int afs_rxvl_get_entry_by_id(afs_server_t *server,
+                                   afs_volid_t volid,
+                                   afs_voltype_t voltype,
+                                   afsc_vldb_record_t *entry);
+
+extern int afs_rxvl_get_entry_by_id_async(afs_async_op_t *op,
+                                         afs_volid_t volid,
+                                         afs_voltype_t voltype);
+
+extern int afs_rxvl_get_entry_by_id_async2(afs_async_op_t *op,
+                                          afsc_vldb_record_t *entry);
+
+#endif /* _LINUX_AFS_VLCLIENT_H */
diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c
new file mode 100644 (file)
index 0000000..8d9f4d7
--- /dev/null
@@ -0,0 +1,824 @@
+/* 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(afs_timer_t *timer);
+static void afs_vlocation_update_attend(afs_async_op_t *op);
+static void afs_vlocation_update_discard(afs_async_op_t *op);
+
+static void __afs_vlocation_timeout(afs_timer_t *timer)
+{
+       afs_vlocation_t *vlocation = list_entry(timer,afs_vlocation_t,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 afs_vlocation_t *afs_vlocation_update;  /* VL currently being updated */
+static spinlock_t afs_vlocation_update_lock = SPIN_LOCK_UNLOCKED; /* lock guarding update queue */
+
+/*****************************************************************************/
+/*
+ * 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(afs_vlocation_t *vlocation,
+                                          const char *name,
+                                          afsc_vldb_record_t *vldb)
+{
+       afs_server_t *server = NULL;
+       afs_cell_t *cell = vlocation->cell;
+       int count, ret;
+
+       _enter("%s,%s,",cell->name,name);
+
+       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,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(afs_vlocation_t *vlocation,
+                                        afs_volid_t volid,
+                                        afs_voltype_t voltype,
+                                        afsc_vldb_record_t *vldb)
+{
+       afs_server_t *server = NULL;
+       afs_cell_t *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(afs_cell_t *cell, const char *name, afs_vlocation_t **_vlocation)
+{
+       afsc_vldb_record_t vldb;
+       struct list_head *_p;
+       afs_vlocation_t *vlocation;
+       afs_voltype_t voltype;
+       afs_volid_t vid;
+       int active = 0, ret;
+
+       _enter(",%s,%s,",cell->name,name);
+
+       if (strlen(name)>sizeof(vlocation->vldb.name)) {
+               _leave(" = -ENAMETOOLONG");
+               return -ENAMETOOLONG;
+       }
+
+       /* search the cell's active list first */
+       list_for_each(_p,&cell->vl_list) {
+               vlocation = list_entry(_p,afs_vlocation_t,link);
+               if (strncmp(vlocation->vldb.name,name,sizeof(vlocation->vldb.name))==0)
+                       goto found_in_memory;
+       }
+
+       /* search the cell's graveyard list second */
+       spin_lock(&cell->vl_gylock);
+       list_for_each(_p,&cell->vl_graveyard) {
+               vlocation = list_entry(_p,afs_vlocation_t,link);
+               if (strncmp(vlocation->vldb.name,name,sizeof(vlocation->vldb.name))==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(afs_vlocation_t),GFP_KERNEL);
+       if (!vlocation)
+               return -ENOMEM;
+
+       memset(vlocation,0,sizeof(afs_vlocation_t));
+       atomic_set(&vlocation->usage,1);
+       INIT_LIST_HEAD(&vlocation->link);
+       rwlock_init(&vlocation->lock);
+       strncpy(vlocation->vldb.name,name,sizeof(vlocation->vldb.name));
+
+       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);
+
+       INIT_LIST_HEAD(&vlocation->caches);
+
+       afs_get_cell(cell);
+       vlocation->cell = cell;
+
+       list_add_tail(&vlocation->link,&cell->vl_list);
+
+#if 0
+       /* search local cache if wasn't in memory */
+       ret = afsc_lookup_vlocation(vlocation);
+       switch (ret) {
+       default:        goto error;             /* disk error */
+       case 0:         goto found_in_cache;    /* pulled from local cache into memory */
+       case -ENOENT:   break;                  /* not in local 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,&vldb);
+       if (ret<0) {
+               printk("kAFS: failed to locate '%s' in cell '%s'\n",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;
+
+/* found_in_cache: */
+       /* 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 & AFSC_VOL_STM_RW) {
+               vid = vlocation->vldb.vid[0];
+               voltype = AFSVL_RWVOL;
+       }
+       else if (vlocation->vldb.vidmask & AFSC_VOL_STM_RO) {
+               vid = vlocation->vldb.vid[1];
+               voltype = AFSVL_ROVOL;
+       }
+       else if (vlocation->vldb.vidmask & AFSC_VOL_STM_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",
+                      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",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) }",
+              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 (strncmp(vldb.name,name,sizeof(vlocation->vldb.name))!=0)
+               printk("kAFS: name of volume '%s' changed to '%s' on server\n",name,vldb.name);
+
+       memcpy(&vlocation->vldb,&vldb,sizeof(vlocation->vldb));
+
+#if 0
+       /* add volume entry to local cache */
+       ret = afsc_update_vlocation(vlocation);
+       if (ret<0)
+               goto error;
+#endif
+
+       afs_kafstimod_add_timer(&vlocation->upd_timer,10*HZ);
+
+       *_vlocation = vlocation;
+       _leave(" = 0 (%p)",vlocation);
+       return 0;
+
+ error:
+       if (vlocation) {
+               if (active) {
+                       __afs_put_vlocation(vlocation);
+               }
+               else {
+                       list_del(&vlocation->link);
+                       afs_put_cell(vlocation->cell);
+#if 0
+                       afs_put_cache(vlocation->cache);
+#endif
+                       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
+ */
+void __afs_put_vlocation(afs_vlocation_t *vlocation)
+{
+       afs_cell_t *cell = vlocation->cell;
+
+       _enter("%s",vlocation->vldb.name);
+
+       /* sanity check */
+       if (atomic_read(&vlocation->usage)<=0)
+               BUG();
+
+       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(afs_vlocation_t *vlocation)
+{
+       afs_cell_t *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(afs_vlocation_t *vlocation)
+{
+       afs_cell_t *cell;
+
+       _enter("%s",vlocation->vldb.name);
+
+       cell = vlocation->cell;
+
+       if (atomic_read(&vlocation->usage)<0) BUG();
+
+       /* 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 */
+       afs_put_cell(cell);
+#if 0
+       afs_put_cache(vlocation->cache);
+#endif
+
+       kfree(vlocation);
+
+       _leave(" [destroyed]");
+} /* end afs_vlocation_do_timeout() */
+
+/*****************************************************************************/
+/*
+ * send an update operation to the currently selected server
+ */
+static int afs_vlocation_update_begin(afs_vlocation_t *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 & AFSC_VOL_STM_RW) {
+               vid = vlocation->vldb.vid[0];
+               voltype = AFSVL_RWVOL;
+       }
+       else if (vlocation->vldb.vidmask & AFSC_VOL_STM_RO) {
+               vid = vlocation->vldb.vid[1];
+               voltype = AFSVL_ROVOL;
+       }
+       else if (vlocation->vldb.vidmask & AFSC_VOL_STM_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(afs_vlocation_t *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 */
+       if (vlocation->upd_op.server) {
+               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(afs_timer_t *timer)
+{
+       afs_vlocation_t *vlocation = list_entry(timer,afs_vlocation_t,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(afs_async_op_t *op)
+{
+       afsc_vldb_record_t vldb;
+       afs_vlocation_t *vlocation = list_entry(op,afs_vlocation_t,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
+               /* add volume entry to local cache */
+               ret = afsc_update_vlocation(vlocation);
+#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;
+
+       if (vlocation->upd_op.server) {
+               /* 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(afs_async_op_t *op)
+{
+       afs_vlocation_t *vlocation = list_entry(op,afs_vlocation_t,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() */
diff --git a/fs/afs/vnode.c b/fs/afs/vnode.c
new file mode 100644 (file)
index 0000000..ab2d7b2
--- /dev/null
@@ -0,0 +1,316 @@
+/* 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,
+};
+
+/*****************************************************************************/
+/*
+ * 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)
+{
+       afs_server_t *oldserver;
+       afs_vnode_t *vnode;
+
+       vnode = list_entry(timer,afs_vnode_t,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);
+
+       if (oldserver)
+               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
+ */
+void afs_vnode_finalise_status_update(afs_vnode_t *vnode, afs_server_t *server, int ret)
+{
+       afs_server_t *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);
+
+       if (oldserver)
+               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(afs_vnode_t *vnode)
+{
+       afs_server_t *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(afs_vnode_t *vnode, struct afs_rxfs_fetch_descriptor *desc)
+{
+       afs_server_t *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(afs_vnode_t *vnode)
+{
+       afs_server_t *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() */
diff --git a/fs/afs/vnode.h b/fs/afs/vnode.h
new file mode 100644 (file)
index 0000000..ec2c412
--- /dev/null
@@ -0,0 +1,88 @@
+/* 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 <linux/version.h>
+#include "server.h"
+#include "kafstimod.h"
+
+#ifdef __KERNEL__
+
+struct afs_rxfs_fetch_descriptor;
+
+/*****************************************************************************/
+/*
+ * AFS inode private data
+ */
+struct afs_vnode
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       struct inode            vfs_inode;      /* the VFS's inode record */
+#else
+       struct inode            *inode;         /* the VFS's inode */
+#endif
+
+       afs_volume_t            *volume;        /* volume on which vnode resides */
+       afs_fid_t               fid;            /* the file identifier for this inode */
+       afs_file_status_t       status;         /* AFS status info for this file */
+       unsigned                nix;            /* vnode index in cache */
+
+       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 */
+       afs_server_t            *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 */
+       afs_timer_t             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 afs_vnode_t *AFS_FS_I(struct inode *inode)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       return list_entry(inode,afs_vnode_t,vfs_inode);
+#else
+       return inode->u.generic_ip;
+#endif
+}
+
+static inline struct inode *AFS_VNODE_TO_I(afs_vnode_t *vnode)
+{
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+       return &vnode->vfs_inode;
+#else
+       return vnode->inode;
+#endif
+}
+
+extern int afs_vnode_fetch_status(afs_vnode_t *vnode);
+
+extern int afs_vnode_fetch_data(afs_vnode_t *vnode, struct afs_rxfs_fetch_descriptor *desc);
+
+extern int afs_vnode_give_up_callback(afs_vnode_t *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 (file)
index 0000000..198c355
--- /dev/null
@@ -0,0 +1,430 @@
+/* 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 "cell.h"
+#include "cmservice.h"
+#include "fsclient.h"
+#include "vlclient.h"
+#include "internal.h"
+
+const char *afs_voltypes[] = { "R/W", "R/O", "BAK" };
+
+/*****************************************************************************/
+/*
+ * 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(char *name, int rwparent, afs_volume_t **_volume)
+{
+       afs_vlocation_t *vlocation = NULL;
+       afs_voltype_t type;
+       afs_volume_t *volume = NULL;
+       afs_cell_t *cell = NULL;
+       char *cellname, *volname, *suffix;
+       char srvtmask;
+       int force, ret, loop;
+
+       _enter(",%s,",name);
+
+       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 (rwparent || 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) {
+                       *suffix = 0;
+                       suffix = NULL;
+               }
+               else {
+                       suffix = NULL;
+               }
+       }
+
+       /* split the cell and volume names */
+       name++;
+       volname = strchr(name,':');
+       if (volname) {
+               *volname++ = 0;
+               cellname = name;
+       }
+       else {
+               volname = name;
+               cellname = NULL;
+       }
+
+       _debug("CELL:%s VOLUME:%s SUFFIX:%s TYPE:%d%s",
+              cellname,volname,suffix?:"-",type,force?" FORCE":"");
+
+       /* lookup the cell record */
+       ret = afs_cell_lookup(cellname,&cell);
+       if (ret<0)
+               printk("kAFS: unable to lookup cell '%s'\n",cellname?:"");
+
+       if (cellname) volname[-1] = ':';
+       if (ret<0)
+               goto error;
+
+       /* lookup the volume location record */
+       if (suffix) *suffix = 0;
+       ret = afs_vlocation_lookup(cell,volname,&vlocation);
+       if (suffix) *suffix = '.';
+       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 & AFSC_VOL_STM_RO) {
+               type = AFSVL_ROVOL;
+       }
+       else if (srvtmask & AFSC_VOL_STM_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(afs_volume_t),GFP_KERNEL);
+       if (!volume)
+               goto error_up;
+
+       memset(volume,0,sizeof(afs_volume_t));
+       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 */
+#if 0
+       afs_get_cache(cache);           volume->cache = 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:
+       if (vlocation)  afs_put_vlocation(vlocation);
+       if (cell)       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--)
+               if (volume->servers[loop])
+                       afs_put_server(volume->servers[loop]);
+
+       kfree(volume);
+       goto error;
+} /* end afs_volume_lookup() */
+
+/*****************************************************************************/
+/*
+ * destroy a volume record
+ */
+void afs_put_volume(afs_volume_t *volume)
+{
+       afs_vlocation_t *vlocation;
+       int loop;
+
+       _enter("%p",volume);
+
+       vlocation = volume->vlocation;
+
+       /* sanity check */
+       if (atomic_read(&volume->usage)<=0)
+               BUG();
+
+       /* 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);
+
+       afs_put_vlocation(vlocation);
+
+       /* finish cleaning up the volume */
+#if 0
+       if (volume->cache)      afs_put_cache(volume->cache);
+#endif
+
+       for (loop=volume->nservers-1; loop>=0; loop--)
+               if (volume->servers[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(afs_volume_t *volume, afs_server_t **_server)
+{
+       afs_server_t *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(afs_volume_t *volume, afs_server_t *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() */
diff --git a/fs/afs/volume.h b/fs/afs/volume.h
new file mode 100644 (file)
index 0000000..1842d98
--- /dev/null
@@ -0,0 +1,92 @@
+/* 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-layout.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;
+
+/*****************************************************************************/
+/*
+ * AFS volume location record
+ */
+struct afs_vlocation
+{
+       atomic_t                usage;
+       struct list_head        link;           /* link in cell volume location list */
+       afs_timer_t             timeout;        /* decaching timer */
+       afs_cell_t              *cell;          /* cell to which volume belongs */
+       struct list_head        caches;         /* backing caches */
+       afsc_vldb_record_t      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 */
+       afs_timer_t             upd_timer;      /* update timer */
+       afs_async_op_t          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(afs_cell_t *cell, const char *name, afs_vlocation_t **_vlocation);
+
+#define afs_get_vlocation(V) do { atomic_inc(&(V)->usage); } while(0)
+
+extern void __afs_put_vlocation(afs_vlocation_t *vlocation);
+extern void afs_put_vlocation(afs_vlocation_t *vlocation);
+extern void afs_vlocation_do_timeout(afs_vlocation_t *vlocation);
+
+/*****************************************************************************/
+/*
+ * AFS volume access record
+ */
+struct afs_volume
+{
+       atomic_t                usage;
+       afs_cell_t              *cell;          /* cell to which belongs (unrefd ptr) */
+       afs_vlocation_t         *vlocation;     /* volume location */
+       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 */
+       afs_server_t            *servers[8];    /* servers on which volume resides (ordered) */
+       struct rw_semaphore     server_sem;     /* lock for accessing current server */
+};
+
+extern int afs_volume_lookup(char *name, int ro, afs_volume_t **_volume);
+
+#define afs_get_volume(V) do { atomic_inc(&(V)->usage); } while(0)
+
+extern void afs_put_volume(afs_volume_t *volume);
+
+extern int afs_volume_pick_fileserver(afs_volume_t *volume, afs_server_t **_server);
+
+extern int afs_volume_release_fileserver(afs_volume_t *volume, afs_server_t *server, int result);
+
+#endif /* _LINUX_AFS_VOLUME_H */