Commit 67bb51b9 authored by David Howells's avatar David Howells Committed by Linus Torvalds

[PATCH] AFS filesystem 2/2

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.
parent 3d970ece
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
......@@ -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.
......@@ -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
......
......@@ -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
#
# 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
/* 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 */
/*
* 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() */
/* 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() */
/* 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 */
/* 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() */
/* 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 */
/* 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() */
/* 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 */
/* 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() */
/* 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
/* 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 */
/*
* 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() */
/* 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 */
/* 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() */
/* 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 */
/* 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() */
/* 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 */
/* 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() */
/* 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() */
/* 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() */
/* 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 */
/* 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() */
/* 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() */
/* 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 */
/*
* 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
/* 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 */
/* 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 */
/* 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 */
/* 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() */
/* 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 */
/* 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() */
/* 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() */
/* 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 */
/* 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() */
/* 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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment