2005-04-17 02:20:36 +04:00
/*
* fs / nfs / nfs4state . c
*
* Client - side XDR for NFSv4 .
*
* Copyright ( c ) 2002 The Regents of the University of Michigan .
* All rights reserved .
*
* Kendrick Smith < kmsmith @ umich . edu >
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
*
* 1. Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED ` ` AS IS ' ' AND ANY EXPRESS OR IMPLIED
* WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR
* CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
* BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING
* NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*
* Implementation of the NFSv4 state model . For the time being ,
* this is minimal , but will be made much more complex in a
* subsequent patch .
*/
# include <linux/config.h>
# include <linux/slab.h>
# include <linux/smp_lock.h>
# include <linux/nfs_fs.h>
# include <linux/nfs_idmap.h>
# include <linux/workqueue.h>
# include <linux/bitops.h>
2005-06-22 21:16:21 +04:00
# include "nfs4_fs.h"
2005-04-17 02:20:36 +04:00
# include "callback.h"
# include "delegation.h"
# define OPENOWNER_POOL_SIZE 8
2005-06-22 21:16:21 +04:00
const nfs4_stateid zero_stateid ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:21 +04:00
static DEFINE_SPINLOCK ( state_spinlock ) ;
2005-04-17 02:20:36 +04:00
static LIST_HEAD ( nfs4_clientid_list ) ;
static void nfs4_recover_state ( void * ) ;
void
init_nfsv4_state ( struct nfs_server * server )
{
server - > nfs4_state = NULL ;
INIT_LIST_HEAD ( & server - > nfs4_siblings ) ;
}
void
destroy_nfsv4_state ( struct nfs_server * server )
{
if ( server - > mnt_path ) {
kfree ( server - > mnt_path ) ;
server - > mnt_path = NULL ;
}
if ( server - > nfs4_state ) {
nfs4_put_client ( server - > nfs4_state ) ;
server - > nfs4_state = NULL ;
}
}
/*
* nfs4_get_client ( ) : returns an empty client structure
* nfs4_put_client ( ) : drops reference to client structure
*
* Since these are allocated / deallocated very rarely , we don ' t
* bother putting them in a slab cache . . .
*/
static struct nfs4_client *
nfs4_alloc_client ( struct in_addr * addr )
{
struct nfs4_client * clp ;
if ( nfs_callback_up ( ) < 0 )
return NULL ;
if ( ( clp = kmalloc ( sizeof ( * clp ) , GFP_KERNEL ) ) = = NULL ) {
nfs_callback_down ( ) ;
return NULL ;
}
memset ( clp , 0 , sizeof ( * clp ) ) ;
memcpy ( & clp - > cl_addr , addr , sizeof ( clp - > cl_addr ) ) ;
init_rwsem ( & clp - > cl_sem ) ;
INIT_LIST_HEAD ( & clp - > cl_delegations ) ;
INIT_LIST_HEAD ( & clp - > cl_state_owners ) ;
INIT_LIST_HEAD ( & clp - > cl_unused ) ;
spin_lock_init ( & clp - > cl_lock ) ;
atomic_set ( & clp - > cl_count , 1 ) ;
INIT_WORK ( & clp - > cl_recoverd , nfs4_recover_state , clp ) ;
INIT_WORK ( & clp - > cl_renewd , nfs4_renew_state , clp ) ;
INIT_LIST_HEAD ( & clp - > cl_superblocks ) ;
init_waitqueue_head ( & clp - > cl_waitq ) ;
rpc_init_wait_queue ( & clp - > cl_rpcwaitq , " NFS4 client " ) ;
2005-06-22 21:16:23 +04:00
clp - > cl_rpcclient = ERR_PTR ( - EINVAL ) ;
2005-04-17 02:20:36 +04:00
clp - > cl_boot_time = CURRENT_TIME ;
clp - > cl_state = 1 < < NFS4CLNT_OK ;
return clp ;
}
static void
nfs4_free_client ( struct nfs4_client * clp )
{
struct nfs4_state_owner * sp ;
while ( ! list_empty ( & clp - > cl_unused ) ) {
sp = list_entry ( clp - > cl_unused . next ,
struct nfs4_state_owner ,
so_list ) ;
list_del ( & sp - > so_list ) ;
kfree ( sp ) ;
}
BUG_ON ( ! list_empty ( & clp - > cl_state_owners ) ) ;
if ( clp - > cl_cred )
put_rpccred ( clp - > cl_cred ) ;
nfs_idmap_delete ( clp ) ;
2005-06-22 21:16:23 +04:00
if ( ! IS_ERR ( clp - > cl_rpcclient ) )
2005-04-17 02:20:36 +04:00
rpc_shutdown_client ( clp - > cl_rpcclient ) ;
kfree ( clp ) ;
nfs_callback_down ( ) ;
}
static struct nfs4_client * __nfs4_find_client ( struct in_addr * addr )
{
struct nfs4_client * clp ;
list_for_each_entry ( clp , & nfs4_clientid_list , cl_servers ) {
if ( memcmp ( & clp - > cl_addr , addr , sizeof ( clp - > cl_addr ) ) = = 0 ) {
atomic_inc ( & clp - > cl_count ) ;
return clp ;
}
}
return NULL ;
}
struct nfs4_client * nfs4_find_client ( struct in_addr * addr )
{
struct nfs4_client * clp ;
spin_lock ( & state_spinlock ) ;
clp = __nfs4_find_client ( addr ) ;
spin_unlock ( & state_spinlock ) ;
return clp ;
}
struct nfs4_client *
nfs4_get_client ( struct in_addr * addr )
{
struct nfs4_client * clp , * new = NULL ;
spin_lock ( & state_spinlock ) ;
for ( ; ; ) {
clp = __nfs4_find_client ( addr ) ;
if ( clp ! = NULL )
break ;
clp = new ;
if ( clp ! = NULL ) {
list_add ( & clp - > cl_servers , & nfs4_clientid_list ) ;
new = NULL ;
break ;
}
spin_unlock ( & state_spinlock ) ;
new = nfs4_alloc_client ( addr ) ;
spin_lock ( & state_spinlock ) ;
if ( new = = NULL )
break ;
}
spin_unlock ( & state_spinlock ) ;
if ( new )
nfs4_free_client ( new ) ;
return clp ;
}
void
nfs4_put_client ( struct nfs4_client * clp )
{
if ( ! atomic_dec_and_lock ( & clp - > cl_count , & state_spinlock ) )
return ;
list_del ( & clp - > cl_servers ) ;
spin_unlock ( & state_spinlock ) ;
BUG_ON ( ! list_empty ( & clp - > cl_superblocks ) ) ;
wake_up_all ( & clp - > cl_waitq ) ;
rpc_wake_up ( & clp - > cl_rpcwaitq ) ;
nfs4_kill_renewd ( clp ) ;
nfs4_free_client ( clp ) ;
}
static int __nfs4_init_client ( struct nfs4_client * clp )
{
int status = nfs4_proc_setclientid ( clp , NFS4_CALLBACK , nfs_callback_tcpport ) ;
if ( status = = 0 )
status = nfs4_proc_setclientid_confirm ( clp ) ;
if ( status = = 0 )
nfs4_schedule_state_renewal ( clp ) ;
return status ;
}
int nfs4_init_client ( struct nfs4_client * clp )
{
return nfs4_map_errors ( __nfs4_init_client ( clp ) ) ;
}
u32
nfs4_alloc_lockowner_id ( struct nfs4_client * clp )
{
return clp - > cl_lockowner_id + + ;
}
static struct nfs4_state_owner *
nfs4_client_grab_unused ( struct nfs4_client * clp , struct rpc_cred * cred )
{
struct nfs4_state_owner * sp = NULL ;
if ( ! list_empty ( & clp - > cl_unused ) ) {
sp = list_entry ( clp - > cl_unused . next , struct nfs4_state_owner , so_list ) ;
atomic_inc ( & sp - > so_count ) ;
sp - > so_cred = cred ;
list_move ( & sp - > so_list , & clp - > cl_state_owners ) ;
clp - > cl_nunused - - ;
}
return sp ;
}
static struct nfs4_state_owner *
nfs4_find_state_owner ( struct nfs4_client * clp , struct rpc_cred * cred )
{
struct nfs4_state_owner * sp , * res = NULL ;
list_for_each_entry ( sp , & clp - > cl_state_owners , so_list ) {
if ( sp - > so_cred ! = cred )
continue ;
atomic_inc ( & sp - > so_count ) ;
/* Move to the head of the list */
list_move ( & sp - > so_list , & clp - > cl_state_owners ) ;
res = sp ;
break ;
}
return res ;
}
/*
* nfs4_alloc_state_owner ( ) : this is called on the OPEN or CREATE path to
* create a new state_owner .
*
*/
static struct nfs4_state_owner *
nfs4_alloc_state_owner ( void )
{
struct nfs4_state_owner * sp ;
sp = kmalloc ( sizeof ( * sp ) , GFP_KERNEL ) ;
if ( ! sp )
return NULL ;
init_MUTEX ( & sp - > so_sema ) ;
sp - > so_seqid = 0 ; /* arbitrary */
INIT_LIST_HEAD ( & sp - > so_states ) ;
INIT_LIST_HEAD ( & sp - > so_delegations ) ;
atomic_set ( & sp - > so_count , 1 ) ;
return sp ;
}
void
nfs4_drop_state_owner ( struct nfs4_state_owner * sp )
{
struct nfs4_client * clp = sp - > so_client ;
spin_lock ( & clp - > cl_lock ) ;
list_del_init ( & sp - > so_list ) ;
spin_unlock ( & clp - > cl_lock ) ;
}
/*
* Note : must be called with clp - > cl_sem held in order to prevent races
* with reboot recovery !
*/
struct nfs4_state_owner * nfs4_get_state_owner ( struct nfs_server * server , struct rpc_cred * cred )
{
struct nfs4_client * clp = server - > nfs4_state ;
struct nfs4_state_owner * sp , * new ;
get_rpccred ( cred ) ;
new = nfs4_alloc_state_owner ( ) ;
spin_lock ( & clp - > cl_lock ) ;
sp = nfs4_find_state_owner ( clp , cred ) ;
if ( sp = = NULL )
sp = nfs4_client_grab_unused ( clp , cred ) ;
if ( sp = = NULL & & new ! = NULL ) {
list_add ( & new - > so_list , & clp - > cl_state_owners ) ;
new - > so_client = clp ;
new - > so_id = nfs4_alloc_lockowner_id ( clp ) ;
new - > so_cred = cred ;
sp = new ;
new = NULL ;
}
spin_unlock ( & clp - > cl_lock ) ;
if ( new )
kfree ( new ) ;
if ( sp ! = NULL )
return sp ;
put_rpccred ( cred ) ;
return NULL ;
}
/*
* Must be called with clp - > cl_sem held in order to avoid races
* with state recovery . . .
*/
void nfs4_put_state_owner ( struct nfs4_state_owner * sp )
{
struct nfs4_client * clp = sp - > so_client ;
struct rpc_cred * cred = sp - > so_cred ;
if ( ! atomic_dec_and_lock ( & sp - > so_count , & clp - > cl_lock ) )
return ;
if ( clp - > cl_nunused > = OPENOWNER_POOL_SIZE )
goto out_free ;
if ( list_empty ( & sp - > so_list ) )
goto out_free ;
list_move ( & sp - > so_list , & clp - > cl_unused ) ;
clp - > cl_nunused + + ;
spin_unlock ( & clp - > cl_lock ) ;
put_rpccred ( cred ) ;
cred = NULL ;
return ;
out_free :
list_del ( & sp - > so_list ) ;
spin_unlock ( & clp - > cl_lock ) ;
put_rpccred ( cred ) ;
kfree ( sp ) ;
}
static struct nfs4_state *
nfs4_alloc_open_state ( void )
{
struct nfs4_state * state ;
state = kmalloc ( sizeof ( * state ) , GFP_KERNEL ) ;
if ( ! state )
return NULL ;
state - > state = 0 ;
state - > nreaders = 0 ;
state - > nwriters = 0 ;
state - > flags = 0 ;
memset ( state - > stateid . data , 0 , sizeof ( state - > stateid . data ) ) ;
atomic_set ( & state - > count , 1 ) ;
INIT_LIST_HEAD ( & state - > lock_states ) ;
init_MUTEX ( & state - > lock_sema ) ;
2005-06-22 21:16:32 +04:00
spin_lock_init ( & state - > state_lock ) ;
2005-04-17 02:20:36 +04:00
return state ;
}
static struct nfs4_state *
__nfs4_find_state ( struct inode * inode , struct rpc_cred * cred , mode_t mode )
{
struct nfs_inode * nfsi = NFS_I ( inode ) ;
struct nfs4_state * state ;
mode & = ( FMODE_READ | FMODE_WRITE ) ;
list_for_each_entry ( state , & nfsi - > open_states , inode_states ) {
if ( state - > owner - > so_cred ! = cred )
continue ;
if ( ( mode & FMODE_READ ) ! = 0 & & state - > nreaders = = 0 )
continue ;
if ( ( mode & FMODE_WRITE ) ! = 0 & & state - > nwriters = = 0 )
continue ;
if ( ( state - > state & mode ) ! = mode )
continue ;
atomic_inc ( & state - > count ) ;
if ( mode & FMODE_READ )
state - > nreaders + + ;
if ( mode & FMODE_WRITE )
state - > nwriters + + ;
return state ;
}
return NULL ;
}
static struct nfs4_state *
__nfs4_find_state_byowner ( struct inode * inode , struct nfs4_state_owner * owner )
{
struct nfs_inode * nfsi = NFS_I ( inode ) ;
struct nfs4_state * state ;
list_for_each_entry ( state , & nfsi - > open_states , inode_states ) {
/* Is this in the process of being freed? */
if ( state - > nreaders = = 0 & & state - > nwriters = = 0 )
continue ;
if ( state - > owner = = owner ) {
atomic_inc ( & state - > count ) ;
return state ;
}
}
return NULL ;
}
struct nfs4_state *
nfs4_find_state ( struct inode * inode , struct rpc_cred * cred , mode_t mode )
{
struct nfs4_state * state ;
spin_lock ( & inode - > i_lock ) ;
state = __nfs4_find_state ( inode , cred , mode ) ;
spin_unlock ( & inode - > i_lock ) ;
return state ;
}
static void
nfs4_free_open_state ( struct nfs4_state * state )
{
kfree ( state ) ;
}
struct nfs4_state *
nfs4_get_open_state ( struct inode * inode , struct nfs4_state_owner * owner )
{
struct nfs4_state * state , * new ;
struct nfs_inode * nfsi = NFS_I ( inode ) ;
spin_lock ( & inode - > i_lock ) ;
state = __nfs4_find_state_byowner ( inode , owner ) ;
spin_unlock ( & inode - > i_lock ) ;
if ( state )
goto out ;
new = nfs4_alloc_open_state ( ) ;
spin_lock ( & inode - > i_lock ) ;
state = __nfs4_find_state_byowner ( inode , owner ) ;
if ( state = = NULL & & new ! = NULL ) {
state = new ;
/* Caller *must* be holding owner->so_sem */
/* Note: The reclaim code dictates that we add stateless
* and read - only stateids to the end of the list */
list_add_tail ( & state - > open_states , & owner - > so_states ) ;
state - > owner = owner ;
atomic_inc ( & owner - > so_count ) ;
list_add ( & state - > inode_states , & nfsi - > open_states ) ;
state - > inode = igrab ( inode ) ;
spin_unlock ( & inode - > i_lock ) ;
} else {
spin_unlock ( & inode - > i_lock ) ;
if ( new )
nfs4_free_open_state ( new ) ;
}
out :
return state ;
}
/*
* Beware ! Caller must be holding exactly one
* reference to clp - > cl_sem and owner - > so_sema !
*/
void nfs4_put_open_state ( struct nfs4_state * state )
{
struct inode * inode = state - > inode ;
struct nfs4_state_owner * owner = state - > owner ;
if ( ! atomic_dec_and_lock ( & state - > count , & inode - > i_lock ) )
return ;
if ( ! list_empty ( & state - > inode_states ) )
list_del ( & state - > inode_states ) ;
spin_unlock ( & inode - > i_lock ) ;
list_del ( & state - > open_states ) ;
iput ( inode ) ;
BUG_ON ( state - > state ! = 0 ) ;
nfs4_free_open_state ( state ) ;
nfs4_put_state_owner ( owner ) ;
}
/*
* Beware ! Caller must be holding no references to clp - > cl_sem !
* of owner - > so_sema !
*/
void nfs4_close_state ( struct nfs4_state * state , mode_t mode )
{
struct inode * inode = state - > inode ;
struct nfs4_state_owner * owner = state - > owner ;
struct nfs4_client * clp = owner - > so_client ;
int newstate ;
atomic_inc ( & owner - > so_count ) ;
down_read ( & clp - > cl_sem ) ;
down ( & owner - > so_sema ) ;
/* Protect against nfs4_find_state() */
spin_lock ( & inode - > i_lock ) ;
if ( mode & FMODE_READ )
state - > nreaders - - ;
if ( mode & FMODE_WRITE )
state - > nwriters - - ;
if ( state - > nwriters = = 0 ) {
if ( state - > nreaders = = 0 )
list_del_init ( & state - > inode_states ) ;
/* See reclaim code */
list_move_tail ( & state - > open_states , & owner - > so_states ) ;
}
spin_unlock ( & inode - > i_lock ) ;
newstate = 0 ;
if ( state - > state ! = 0 ) {
if ( state - > nreaders )
newstate | = FMODE_READ ;
if ( state - > nwriters )
newstate | = FMODE_WRITE ;
if ( state - > state = = newstate )
goto out ;
if ( nfs4_do_close ( inode , state , newstate ) = = - EINPROGRESS )
return ;
}
out :
nfs4_put_open_state ( state ) ;
up ( & owner - > so_sema ) ;
nfs4_put_state_owner ( owner ) ;
up_read ( & clp - > cl_sem ) ;
}
/*
* Search the state - > lock_states for an existing lock_owner
* that is compatible with current - > files
*/
static struct nfs4_lock_state *
__nfs4_find_lock_state ( struct nfs4_state * state , fl_owner_t fl_owner )
{
struct nfs4_lock_state * pos ;
list_for_each_entry ( pos , & state - > lock_states , ls_locks ) {
if ( pos - > ls_owner ! = fl_owner )
continue ;
atomic_inc ( & pos - > ls_count ) ;
return pos ;
}
return NULL ;
}
/*
* Return a compatible lock_state . If no initialized lock_state structure
* exists , return an uninitialized one .
*
* The caller must be holding state - > lock_sema
*/
static struct nfs4_lock_state * nfs4_alloc_lock_state ( struct nfs4_state * state , fl_owner_t fl_owner )
{
struct nfs4_lock_state * lsp ;
struct nfs4_client * clp = state - > owner - > so_client ;
lsp = kmalloc ( sizeof ( * lsp ) , GFP_KERNEL ) ;
if ( lsp = = NULL )
return NULL ;
lsp - > ls_flags = 0 ;
lsp - > ls_seqid = 0 ; /* arbitrary */
memset ( lsp - > ls_stateid . data , 0 , sizeof ( lsp - > ls_stateid . data ) ) ;
atomic_set ( & lsp - > ls_count , 1 ) ;
lsp - > ls_owner = fl_owner ;
spin_lock ( & clp - > cl_lock ) ;
lsp - > ls_id = nfs4_alloc_lockowner_id ( clp ) ;
spin_unlock ( & clp - > cl_lock ) ;
2005-06-22 21:16:32 +04:00
INIT_LIST_HEAD ( & lsp - > ls_locks ) ;
2005-04-17 02:20:36 +04:00
return lsp ;
}
/*
* Return a compatible lock_state . If no initialized lock_state structure
* exists , return an uninitialized one .
*
* The caller must be holding state - > lock_sema and clp - > cl_sem
*/
2005-06-22 21:16:32 +04:00
static struct nfs4_lock_state * nfs4_get_lock_state ( struct nfs4_state * state , fl_owner_t owner )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
struct nfs4_lock_state * lsp , * new = NULL ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
for ( ; ; ) {
spin_lock ( & state - > state_lock ) ;
lsp = __nfs4_find_lock_state ( state , owner ) ;
if ( lsp ! = NULL )
break ;
if ( new ! = NULL ) {
new - > ls_state = state ;
list_add ( & new - > ls_locks , & state - > lock_states ) ;
set_bit ( LK_STATE_IN_USE , & state - > flags ) ;
lsp = new ;
new = NULL ;
break ;
}
spin_unlock ( & state - > state_lock ) ;
new = nfs4_alloc_lock_state ( state , owner ) ;
if ( new = = NULL )
return NULL ;
}
spin_unlock ( & state - > state_lock ) ;
kfree ( new ) ;
2005-04-17 02:20:36 +04:00
return lsp ;
}
/*
2005-06-22 21:16:32 +04:00
* Release reference to lock_state , and free it if we see that
* it is no longer in use
2005-04-17 02:20:36 +04:00
*/
2005-06-22 21:16:32 +04:00
static void nfs4_put_lock_state ( struct nfs4_lock_state * lsp )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
struct nfs4_state * state ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
if ( lsp = = NULL )
return ;
state = lsp - > ls_state ;
if ( ! atomic_dec_and_lock ( & lsp - > ls_count , & state - > state_lock ) )
return ;
list_del ( & lsp - > ls_locks ) ;
if ( list_empty ( & state - > lock_states ) )
clear_bit ( LK_STATE_IN_USE , & state - > flags ) ;
spin_unlock ( & state - > state_lock ) ;
kfree ( lsp ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-22 21:16:32 +04:00
static void nfs4_fl_copy_lock ( struct file_lock * dst , struct file_lock * src )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
struct nfs4_lock_state * lsp = src - > fl_u . nfs4_fl . owner ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
dst - > fl_u . nfs4_fl . owner = lsp ;
atomic_inc ( & lsp - > ls_count ) ;
}
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
static void nfs4_fl_release_lock ( struct file_lock * fl )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
nfs4_put_lock_state ( fl - > fl_u . nfs4_fl . owner ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-22 21:16:32 +04:00
static struct file_lock_operations nfs4_fl_lock_ops = {
. fl_copy_lock = nfs4_fl_copy_lock ,
. fl_release_private = nfs4_fl_release_lock ,
} ;
int nfs4_set_lock_state ( struct nfs4_state * state , struct file_lock * fl )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
struct nfs4_lock_state * lsp ;
if ( fl - > fl_ops ! = NULL )
return 0 ;
lsp = nfs4_get_lock_state ( state , fl - > fl_owner ) ;
if ( lsp = = NULL )
return - ENOMEM ;
fl - > fl_u . nfs4_fl . owner = lsp ;
fl - > fl_ops = & nfs4_fl_lock_ops ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-06-22 21:16:32 +04:00
/*
* Byte - range lock aware utility to initialize the stateid of read / write
* requests .
2005-04-17 02:20:36 +04:00
*/
2005-06-22 21:16:32 +04:00
void nfs4_copy_stateid ( nfs4_stateid * dst , struct nfs4_state * state , fl_owner_t fl_owner )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
struct nfs4_lock_state * lsp ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
memcpy ( dst , & state - > stateid , sizeof ( * dst ) ) ;
if ( test_bit ( LK_STATE_IN_USE , & state - > flags ) = = 0 )
return ;
2005-04-17 02:20:36 +04:00
2005-06-22 21:16:32 +04:00
spin_lock ( & state - > state_lock ) ;
lsp = __nfs4_find_lock_state ( state , fl_owner ) ;
if ( lsp ! = NULL & & ( lsp - > ls_flags & NFS_LOCK_INITIALIZED ) ! = 0 )
memcpy ( dst , & lsp - > ls_stateid , sizeof ( * dst ) ) ;
spin_unlock ( & state - > state_lock ) ;
2005-04-17 02:20:36 +04:00
nfs4_put_lock_state ( lsp ) ;
}
/*
2005-06-22 21:16:32 +04:00
* Called with state - > lock_sema and clp - > cl_sem held .
*/
void nfs4_increment_lock_seqid ( int status , struct nfs4_lock_state * lsp )
2005-04-17 02:20:36 +04:00
{
2005-06-22 21:16:32 +04:00
if ( status = = NFS_OK | | seqid_mutating_err ( - status ) )
lsp - > ls_seqid + + ;
2005-04-17 02:20:36 +04:00
}
/*
* Called with sp - > so_sema and clp - > cl_sem held .
*
* Increment the seqid if the OPEN / OPEN_DOWNGRADE / CLOSE succeeded , or
* failed with a seqid incrementing error -
* see comments nfs_fs . h : seqid_mutating_error ( )
*/
void nfs4_increment_seqid ( int status , struct nfs4_state_owner * sp )
{
if ( status = = NFS_OK | | seqid_mutating_err ( - status ) )
sp - > so_seqid + + ;
/* If the server returns BAD_SEQID, unhash state_owner here */
if ( status = = - NFS4ERR_BAD_SEQID )
nfs4_drop_state_owner ( sp ) ;
}
static int reclaimer ( void * ) ;
struct reclaimer_args {
struct nfs4_client * clp ;
struct completion complete ;
} ;
/*
* State recovery routine
*/
void
nfs4_recover_state ( void * data )
{
struct nfs4_client * clp = ( struct nfs4_client * ) data ;
struct reclaimer_args args = {
. clp = clp ,
} ;
might_sleep ( ) ;
init_completion ( & args . complete ) ;
if ( kernel_thread ( reclaimer , & args , CLONE_KERNEL ) < 0 )
goto out_failed_clear ;
wait_for_completion ( & args . complete ) ;
return ;
out_failed_clear :
set_bit ( NFS4CLNT_OK , & clp - > cl_state ) ;
wake_up_all ( & clp - > cl_waitq ) ;
rpc_wake_up ( & clp - > cl_rpcwaitq ) ;
}
/*
* Schedule a state recovery attempt
*/
void
nfs4_schedule_state_recovery ( struct nfs4_client * clp )
{
if ( ! clp )
return ;
if ( test_and_clear_bit ( NFS4CLNT_OK , & clp - > cl_state ) )
schedule_work ( & clp - > cl_recoverd ) ;
}
static int nfs4_reclaim_locks ( struct nfs4_state_recovery_ops * ops , struct nfs4_state * state )
{
struct inode * inode = state - > inode ;
struct file_lock * fl ;
int status = 0 ;
for ( fl = inode - > i_flock ; fl ! = 0 ; fl = fl - > fl_next ) {
if ( ! ( fl - > fl_flags & FL_POSIX ) )
continue ;
if ( ( ( struct nfs_open_context * ) fl - > fl_file - > private_data ) - > state ! = state )
continue ;
status = ops - > recover_lock ( state , fl ) ;
if ( status > = 0 )
continue ;
switch ( status ) {
default :
printk ( KERN_ERR " %s: unhandled error %d. Zeroing state \n " ,
__FUNCTION__ , status ) ;
case - NFS4ERR_EXPIRED :
case - NFS4ERR_NO_GRACE :
case - NFS4ERR_RECLAIM_BAD :
case - NFS4ERR_RECLAIM_CONFLICT :
/* kill_proc(fl->fl_owner, SIGLOST, 1); */
break ;
case - NFS4ERR_STALE_CLIENTID :
goto out_err ;
}
}
return 0 ;
out_err :
return status ;
}
static int nfs4_reclaim_open_state ( struct nfs4_state_recovery_ops * ops , struct nfs4_state_owner * sp )
{
struct nfs4_state * state ;
struct nfs4_lock_state * lock ;
int status = 0 ;
/* Note: we rely on the sp->so_states list being ordered
* so that we always reclaim open ( O_RDWR ) and / or open ( O_WRITE )
* states first .
* This is needed to ensure that the server won ' t give us any
* read delegations that we have to return if , say , we are
* recovering after a network partition or a reboot from a
* server that doesn ' t support a grace period .
*/
list_for_each_entry ( state , & sp - > so_states , open_states ) {
if ( state - > state = = 0 )
continue ;
status = ops - > recover_open ( sp , state ) ;
list_for_each_entry ( lock , & state - > lock_states , ls_locks )
lock - > ls_flags & = ~ NFS_LOCK_INITIALIZED ;
if ( status > = 0 ) {
status = nfs4_reclaim_locks ( ops , state ) ;
if ( status < 0 )
goto out_err ;
list_for_each_entry ( lock , & state - > lock_states , ls_locks ) {
if ( ! ( lock - > ls_flags & NFS_LOCK_INITIALIZED ) )
printk ( " %s: Lock reclaim failed! \n " ,
__FUNCTION__ ) ;
}
continue ;
}
switch ( status ) {
default :
printk ( KERN_ERR " %s: unhandled error %d. Zeroing state \n " ,
__FUNCTION__ , status ) ;
case - ENOENT :
case - NFS4ERR_RECLAIM_BAD :
case - NFS4ERR_RECLAIM_CONFLICT :
/*
* Open state on this file cannot be recovered
* All we can do is revert to using the zero stateid .
*/
memset ( state - > stateid . data , 0 ,
sizeof ( state - > stateid . data ) ) ;
/* Mark the file as being 'closed' */
state - > state = 0 ;
break ;
case - NFS4ERR_EXPIRED :
case - NFS4ERR_NO_GRACE :
case - NFS4ERR_STALE_CLIENTID :
goto out_err ;
}
}
return 0 ;
out_err :
return status ;
}
static int reclaimer ( void * ptr )
{
struct reclaimer_args * args = ( struct reclaimer_args * ) ptr ;
struct nfs4_client * clp = args - > clp ;
struct nfs4_state_owner * sp ;
struct nfs4_state_recovery_ops * ops ;
int status = 0 ;
daemonize ( " %u.%u.%u.%u-reclaim " , NIPQUAD ( clp - > cl_addr ) ) ;
allow_signal ( SIGKILL ) ;
atomic_inc ( & clp - > cl_count ) ;
complete ( & args - > complete ) ;
/* Ensure exclusive access to NFSv4 state */
lock_kernel ( ) ;
down_write ( & clp - > cl_sem ) ;
/* Are there any NFS mounts out there? */
if ( list_empty ( & clp - > cl_superblocks ) )
goto out ;
restart_loop :
status = nfs4_proc_renew ( clp ) ;
switch ( status ) {
case 0 :
case - NFS4ERR_CB_PATH_DOWN :
goto out ;
case - NFS4ERR_STALE_CLIENTID :
case - NFS4ERR_LEASE_MOVED :
ops = & nfs4_reboot_recovery_ops ;
break ;
default :
ops = & nfs4_network_partition_recovery_ops ;
} ;
status = __nfs4_init_client ( clp ) ;
if ( status )
goto out_error ;
/* Mark all delegations for reclaim */
nfs_delegation_mark_reclaim ( clp ) ;
/* Note: list is protected by exclusive lock on cl->cl_sem */
list_for_each_entry ( sp , & clp - > cl_state_owners , so_list ) {
status = nfs4_reclaim_open_state ( ops , sp ) ;
if ( status < 0 ) {
if ( status = = - NFS4ERR_NO_GRACE ) {
ops = & nfs4_network_partition_recovery_ops ;
status = nfs4_reclaim_open_state ( ops , sp ) ;
}
if ( status = = - NFS4ERR_STALE_CLIENTID )
goto restart_loop ;
if ( status = = - NFS4ERR_EXPIRED )
goto restart_loop ;
}
}
nfs_delegation_reap_unclaimed ( clp ) ;
out :
set_bit ( NFS4CLNT_OK , & clp - > cl_state ) ;
up_write ( & clp - > cl_sem ) ;
unlock_kernel ( ) ;
wake_up_all ( & clp - > cl_waitq ) ;
rpc_wake_up ( & clp - > cl_rpcwaitq ) ;
if ( status = = - NFS4ERR_CB_PATH_DOWN )
nfs_handle_cb_pathdown ( clp ) ;
nfs4_put_client ( clp ) ;
return 0 ;
out_error :
printk ( KERN_WARNING " Error: state recovery failed on NFSv4 server %u.%u.%u.%u with error %d \n " ,
NIPQUAD ( clp - > cl_addr . s_addr ) , - status ) ;
goto out ;
}
/*
* Local variables :
* c - basic - offset : 8
* End :
*/