2007-04-27 02:49:28 +04:00
/* AFS server record management
2005-04-17 02:20:36 +04:00
*
2007-04-27 02:55:03 +04:00
* Copyright ( C ) 2002 , 2007 Red Hat , Inc . All Rights Reserved .
2005-04-17 02:20:36 +04:00
* 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 "internal.h"
2007-10-17 10:26:41 +04:00
static unsigned afs_server_timeout = 10 ; /* server timeout in seconds */
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
static void afs_reap_server ( struct work_struct * ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/* tree of all the servers, indexed by IP address */
static struct rb_root afs_servers = RB_ROOT ;
static DEFINE_RWLOCK ( afs_servers_lock ) ;
/* LRU list of all the servers not currently in use */
static LIST_HEAD ( afs_server_graveyard ) ;
static DEFINE_SPINLOCK ( afs_server_graveyard_lock ) ;
static DECLARE_DELAYED_WORK ( afs_server_reaper , afs_reap_server ) ;
/*
* install a server record in the master tree
*/
static int afs_install_server ( struct afs_server * server )
2005-04-17 02:20:36 +04:00
{
2007-04-27 02:55:03 +04:00
struct afs_server * xserver ;
struct rb_node * * pp , * p ;
int ret ;
_enter ( " %p " , server ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
write_lock ( & afs_servers_lock ) ;
ret = - EEXIST ;
pp = & afs_servers . rb_node ;
p = NULL ;
while ( * pp ) {
p = * pp ;
_debug ( " - consider %p " , p ) ;
xserver = rb_entry ( p , struct afs_server , master_rb ) ;
if ( server - > addr . s_addr < xserver - > addr . s_addr )
pp = & ( * pp ) - > rb_left ;
else if ( server - > addr . s_addr > xserver - > addr . s_addr )
pp = & ( * pp ) - > rb_right ;
else
goto error ;
}
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
rb_link_node ( & server - > master_rb , p , pp ) ;
rb_insert_color ( & server - > master_rb , & afs_servers ) ;
ret = 0 ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
error :
write_unlock ( & afs_servers_lock ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
/*
2007-04-27 02:55:03 +04:00
* allocate a new server record
2005-04-17 02:20:36 +04:00
*/
2007-04-27 02:55:03 +04:00
static struct afs_server * afs_alloc_server ( struct afs_cell * cell ,
const struct in_addr * addr )
2005-04-17 02:20:36 +04:00
{
2007-04-27 02:55:03 +04:00
struct afs_server * server ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
_enter ( " " ) ;
2005-04-17 02:20:36 +04:00
2006-12-07 07:40:32 +03:00
server = kzalloc ( sizeof ( struct afs_server ) , GFP_KERNEL ) ;
2007-04-27 02:55:03 +04:00
if ( server ) {
atomic_set ( & server - > usage , 1 ) ;
server - > cell = cell ;
INIT_LIST_HEAD ( & server - > link ) ;
INIT_LIST_HEAD ( & server - > grave ) ;
init_rwsem ( & server - > sem ) ;
spin_lock_init ( & server - > fs_lock ) ;
server - > fs_vnodes = RB_ROOT ;
server - > cb_promises = RB_ROOT ;
spin_lock_init ( & server - > cb_lock ) ;
init_waitqueue_head ( & server - > cb_break_waitq ) ;
INIT_DELAYED_WORK ( & server - > cb_break_work ,
afs_dispatch_give_up_callbacks ) ;
memcpy ( & server - > addr , addr , sizeof ( struct in_addr ) ) ;
server - > addr . s_addr = addr - > s_addr ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
_leave ( " = %p{%d} " , server , atomic_read ( & server - > usage ) ) ;
return server ;
}
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/*
* get an FS - server record for a cell
*/
struct afs_server * afs_lookup_server ( struct afs_cell * cell ,
const struct in_addr * addr )
{
struct afs_server * server , * candidate ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
_enter ( " %p, " NIPQUAD_FMT , cell , NIPQUAD ( addr - > s_addr ) ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/* quick scan of the list to see if we already have the server */
read_lock ( & cell - > servers_lock ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
list_for_each_entry ( server , & cell - > servers , link ) {
if ( server - > addr . s_addr = = addr - > s_addr )
goto found_server_quickly ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
read_unlock ( & cell - > servers_lock ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
candidate = afs_alloc_server ( cell , addr ) ;
if ( ! candidate ) {
_leave ( " = -ENOMEM " ) ;
return ERR_PTR ( - ENOMEM ) ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
write_lock ( & cell - > servers_lock ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/* check the cell's server list again */
list_for_each_entry ( server , & cell - > servers , link ) {
if ( server - > addr . s_addr = = addr - > s_addr )
goto found_server ;
}
_debug ( " new " ) ;
server = candidate ;
if ( afs_install_server ( server ) < 0 )
goto server_in_two_cells ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
afs_get_cell ( cell ) ;
list_add_tail ( & server - > link , & cell - > servers ) ;
write_unlock ( & cell - > servers_lock ) ;
_leave ( " = %p{%d} " , server , atomic_read ( & server - > usage ) ) ;
return server ;
/* found a matching server quickly */
found_server_quickly :
_debug ( " found quickly " ) ;
afs_get_server ( server ) ;
read_unlock ( & cell - > servers_lock ) ;
no_longer_unused :
if ( ! list_empty ( & server - > grave ) ) {
spin_lock ( & afs_server_graveyard_lock ) ;
list_del_init ( & server - > grave ) ;
spin_unlock ( & afs_server_graveyard_lock ) ;
}
_leave ( " = %p{%d} " , server , atomic_read ( & server - > usage ) ) ;
return server ;
/* found a matching server on the second pass */
found_server :
_debug ( " found " ) ;
afs_get_server ( server ) ;
write_unlock ( & cell - > servers_lock ) ;
kfree ( candidate ) ;
goto no_longer_unused ;
/* found a server that seems to be in two cells */
server_in_two_cells :
write_unlock ( & cell - > servers_lock ) ;
kfree ( candidate ) ;
printk ( KERN_NOTICE " kAFS: "
" Server " NIPQUAD_FMT " appears to be in two cells \n " ,
NIPQUAD ( * addr ) ) ;
_leave ( " = -EEXIST " ) ;
return ERR_PTR ( - EEXIST ) ;
}
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/*
* look up a server by its IP address
*/
struct afs_server * afs_find_server ( const struct in_addr * _addr )
{
struct afs_server * server = NULL ;
struct rb_node * p ;
struct in_addr addr = * _addr ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
_enter ( NIPQUAD_FMT , NIPQUAD ( addr . s_addr ) ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
read_lock ( & afs_servers_lock ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
p = afs_servers . rb_node ;
while ( p ) {
server = rb_entry ( p , struct afs_server , master_rb ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
_debug ( " - consider %p " , p ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
if ( addr . s_addr < server - > addr . s_addr ) {
p = p - > rb_left ;
} else if ( addr . s_addr > server - > addr . s_addr ) {
p = p - > rb_right ;
} else {
afs_get_server ( server ) ;
goto found ;
}
}
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
server = NULL ;
found :
read_unlock ( & afs_servers_lock ) ;
ASSERTIFCMP ( server , server - > addr . s_addr , = = , addr . s_addr ) ;
_leave ( " = %p " , server ) ;
return server ;
2007-04-27 02:49:28 +04:00
}
2005-04-17 02:20:36 +04:00
/*
* destroy a server record
* - removes from the cell list
*/
void afs_put_server ( struct afs_server * server )
{
if ( ! server )
return ;
2007-04-27 02:55:03 +04:00
_enter ( " %p{%d} " , server , atomic_read ( & server - > usage ) ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:59:35 +04:00
_debug ( " PUT SERVER %d " , atomic_read ( & server - > usage ) ) ;
2007-04-27 02:55:03 +04:00
ASSERTCMP ( atomic_read ( & server - > usage ) , > , 0 ) ;
2005-04-17 02:20:36 +04:00
if ( likely ( ! atomic_dec_and_test ( & server - > usage ) ) ) {
_leave ( " " ) ;
return ;
}
2007-04-27 02:55:03 +04:00
afs_flush_callback_breaks ( server ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
spin_lock ( & afs_server_graveyard_lock ) ;
if ( atomic_read ( & server - > usage ) = = 0 ) {
list_move_tail ( & server - > grave , & afs_server_graveyard ) ;
server - > time_of_death = get_seconds ( ) ;
schedule_delayed_work ( & afs_server_reaper ,
afs_server_timeout * HZ ) ;
}
spin_unlock ( & afs_server_graveyard_lock ) ;
_leave ( " [dead] " ) ;
2007-04-27 02:49:28 +04:00
}
2005-04-17 02:20:36 +04:00
/*
2007-04-27 02:55:03 +04:00
* destroy a dead server
2005-04-17 02:20:36 +04:00
*/
2007-04-27 02:55:03 +04:00
static void afs_destroy_server ( struct afs_server * server )
2005-04-17 02:20:36 +04:00
{
_enter ( " %p " , server ) ;
2007-05-09 13:33:45 +04:00
ASSERTIF ( server - > cb_break_head ! = server - > cb_break_tail ,
delayed_work_pending ( & server - > cb_break_work ) ) ;
2007-04-27 02:55:03 +04:00
ASSERTCMP ( server - > fs_vnodes . rb_node , = = , NULL ) ;
ASSERTCMP ( server - > cb_promises . rb_node , = = , NULL ) ;
ASSERTCMP ( server - > cb_break_head , = = , server - > cb_break_tail ) ;
ASSERTCMP ( atomic_read ( & server - > cb_break_n ) , = = , 0 ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
afs_put_cell ( server - > cell ) ;
2005-04-17 02:20:36 +04:00
kfree ( server ) ;
2007-04-27 02:49:28 +04:00
}
2005-04-17 02:20:36 +04:00
/*
2007-04-27 02:55:03 +04:00
* reap dead server records
2005-04-17 02:20:36 +04:00
*/
2007-04-27 02:55:03 +04:00
static void afs_reap_server ( struct work_struct * work )
2005-04-17 02:20:36 +04:00
{
2007-04-27 02:55:03 +04:00
LIST_HEAD ( corpses ) ;
struct afs_server * server ;
unsigned long delay , expiry ;
time_t now ;
now = get_seconds ( ) ;
spin_lock ( & afs_server_graveyard_lock ) ;
while ( ! list_empty ( & afs_server_graveyard ) ) {
server = list_entry ( afs_server_graveyard . next ,
struct afs_server , grave ) ;
/* the queue is ordered most dead first */
expiry = server - > time_of_death + afs_server_timeout ;
if ( expiry > now ) {
delay = ( expiry - now ) * HZ ;
if ( ! schedule_delayed_work ( & afs_server_reaper , delay ) ) {
cancel_delayed_work ( & afs_server_reaper ) ;
schedule_delayed_work ( & afs_server_reaper ,
delay ) ;
}
break ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
write_lock ( & server - > cell - > servers_lock ) ;
write_lock ( & afs_servers_lock ) ;
if ( atomic_read ( & server - > usage ) > 0 ) {
list_del_init ( & server - > grave ) ;
} else {
list_move_tail ( & server - > grave , & corpses ) ;
list_del_init ( & server - > link ) ;
rb_erase ( & server - > master_rb , & afs_servers ) ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
write_unlock ( & afs_servers_lock ) ;
write_unlock ( & server - > cell - > servers_lock ) ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:55:03 +04:00
spin_unlock ( & afs_server_graveyard_lock ) ;
2005-04-17 02:20:36 +04:00
2007-04-27 02:55:03 +04:00
/* now reap the corpses we've extracted */
while ( ! list_empty ( & corpses ) ) {
server = list_entry ( corpses . next , struct afs_server , grave ) ;
list_del ( & server - > grave ) ;
afs_destroy_server ( server ) ;
2005-04-17 02:20:36 +04:00
}
2007-04-27 02:49:28 +04:00
}
2005-04-17 02:20:36 +04:00
/*
2007-04-27 02:55:03 +04:00
* discard all the server records for rmmod
2005-04-17 02:20:36 +04:00
*/
2007-04-27 02:55:03 +04:00
void __exit afs_purge_servers ( void )
2005-04-17 02:20:36 +04:00
{
2007-04-27 02:55:03 +04:00
afs_server_timeout = 0 ;
cancel_delayed_work ( & afs_server_reaper ) ;
schedule_delayed_work ( & afs_server_reaper , 0 ) ;
2007-04-27 02:49:28 +04:00
}