2005-04-17 02:20:36 +04:00
/*
* linux / fs / nfs / unlink . c
*
* nfs sillydelete handling
*
*/
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/dcache.h>
# include <linux/sunrpc/sched.h>
# include <linux/sunrpc/clnt.h>
# include <linux/nfs_fs.h>
2007-10-20 06:59:18 +04:00
# include <linux/sched.h>
# include <linux/wait.h>
2005-04-17 02:20:36 +04:00
2007-11-08 12:05:04 +03:00
# include "internal.h"
2005-04-17 02:20:36 +04:00
struct nfs_unlinkdata {
2007-10-16 02:17:53 +04:00
struct hlist_node list ;
2007-07-14 23:39:58 +04:00
struct nfs_removeargs args ;
struct nfs_removeres res ;
struct inode * dir ;
2005-04-17 02:20:36 +04:00
struct rpc_cred * cred ;
} ;
/**
2007-07-14 23:39:58 +04:00
* nfs_free_unlinkdata - release data from a sillydelete operation .
2005-04-17 02:20:36 +04:00
* @ data : pointer to unlink structure .
*/
static void
2007-07-14 23:39:58 +04:00
nfs_free_unlinkdata ( struct nfs_unlinkdata * data )
2005-04-17 02:20:36 +04:00
{
2007-07-14 23:39:58 +04:00
iput ( data - > dir ) ;
put_rpccred ( data - > cred ) ;
kfree ( data - > args . name . name ) ;
kfree ( data ) ;
2005-04-17 02:20:36 +04:00
}
# define NAME_ALLOC_LEN(len) ((len+16) & ~15)
/**
* nfs_copy_dname - copy dentry name to data structure
* @ dentry : pointer to dentry
* @ data : nfs_unlinkdata
*/
2007-07-14 23:39:58 +04:00
static int nfs_copy_dname ( struct dentry * dentry , struct nfs_unlinkdata * data )
2005-04-17 02:20:36 +04:00
{
char * str ;
int len = dentry - > d_name . len ;
2007-07-14 23:39:58 +04:00
str = kmemdup ( dentry - > d_name . name , NAME_ALLOC_LEN ( len ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! str )
2007-07-14 23:39:58 +04:00
return - ENOMEM ;
data - > args . name . len = len ;
data - > args . name . name = str ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-10-16 02:17:53 +04:00
static void nfs_free_dname ( struct nfs_unlinkdata * data )
{
kfree ( data - > args . name . name ) ;
data - > args . name . name = NULL ;
data - > args . name . len = 0 ;
}
static void nfs_dec_sillycount ( struct inode * dir )
{
struct nfs_inode * nfsi = NFS_I ( dir ) ;
if ( atomic_dec_return ( & nfsi - > silly_count ) = = 1 )
wake_up ( & nfsi - > waitqueue ) ;
}
2005-04-17 02:20:36 +04:00
/**
* nfs_async_unlink_done - Sillydelete post - processing
* @ task : rpc_task of the sillydelete
*
* Do the directory attribute update .
*/
2006-01-03 11:55:04 +03:00
static void nfs_async_unlink_done ( struct rpc_task * task , void * calldata )
2005-04-17 02:20:36 +04:00
{
2007-07-14 23:39:58 +04:00
struct nfs_unlinkdata * data = calldata ;
struct inode * dir = data - > dir ;
if ( ! NFS_PROTO ( dir ) - > unlink_done ( task , dir ) )
rpc_restart_call ( task ) ;
2005-04-17 02:20:36 +04:00
}
/**
* nfs_async_unlink_release - Release the sillydelete data .
* @ task : rpc_task of the sillydelete
*
* We need to call nfs_put_unlinkdata as a ' tk_release ' task since the
* rpc_task would be freed too .
*/
2006-01-03 11:55:04 +03:00
static void nfs_async_unlink_release ( void * calldata )
2005-04-17 02:20:36 +04:00
{
2006-01-03 11:55:04 +03:00
struct nfs_unlinkdata * data = calldata ;
2007-10-16 02:17:53 +04:00
nfs_dec_sillycount ( data - > dir ) ;
2007-11-08 12:05:04 +03:00
nfs_sb_deactive ( NFS_SERVER ( data - > dir ) ) ;
2007-07-14 23:39:58 +04:00
nfs_free_unlinkdata ( data ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-03 11:55:04 +03:00
static const struct rpc_call_ops nfs_unlink_ops = {
. rpc_call_done = nfs_async_unlink_done ,
. rpc_release = nfs_async_unlink_release ,
} ;
2007-10-16 02:17:53 +04:00
static int nfs_do_call_unlink ( struct dentry * parent , struct inode * dir , struct nfs_unlinkdata * data )
2007-07-14 23:39:58 +04:00
{
2007-07-14 23:40:01 +04:00
struct rpc_message msg = {
. rpc_argp = & data - > args ,
. rpc_resp = & data - > res ,
. rpc_cred = data - > cred ,
} ;
2007-07-14 23:39:59 +04:00
struct rpc_task_setup task_setup_data = {
2007-07-14 23:40:01 +04:00
. rpc_message = & msg ,
2007-07-14 23:39:59 +04:00
. callback_ops = & nfs_unlink_ops ,
. callback_data = data ,
. flags = RPC_TASK_ASYNC ,
} ;
2007-07-14 23:39:58 +04:00
struct rpc_task * task ;
2007-10-16 02:17:53 +04:00
struct dentry * alias ;
alias = d_lookup ( parent , & data - > args . name ) ;
if ( alias ! = NULL ) {
int ret = 0 ;
2008-01-29 03:42:59 +03:00
2007-10-16 02:17:53 +04:00
/*
* Hey , we raced with lookup . . . See if we need to transfer
* the sillyrename information to the aliased dentry .
*/
nfs_free_dname ( data ) ;
spin_lock ( & alias - > d_lock ) ;
2008-01-29 03:42:59 +03:00
if ( alias - > d_inode ! = NULL & &
! ( alias - > d_flags & DCACHE_NFSFS_RENAMED ) ) {
2007-10-16 02:17:53 +04:00
alias - > d_fsdata = data ;
2008-01-27 01:37:47 +03:00
alias - > d_flags | = DCACHE_NFSFS_RENAMED ;
2007-10-16 02:17:53 +04:00
ret = 1 ;
}
spin_unlock ( & alias - > d_lock ) ;
nfs_dec_sillycount ( dir ) ;
dput ( alias ) ;
return ret ;
}
data - > dir = igrab ( dir ) ;
if ( ! data - > dir ) {
nfs_dec_sillycount ( dir ) ;
return 0 ;
}
2007-11-08 12:05:04 +03:00
nfs_sb_active ( NFS_SERVER ( dir ) ) ;
2007-10-16 02:17:53 +04:00
data - > args . fh = NFS_FH ( dir ) ;
nfs_fattr_init ( & data - > res . dir_attr ) ;
2007-07-14 23:40:01 +04:00
NFS_PROTO ( dir ) - > unlink_setup ( & msg , dir ) ;
2007-07-14 23:39:59 +04:00
2007-07-14 23:40:01 +04:00
task_setup_data . rpc_client = NFS_CLIENT ( dir ) ;
2007-07-14 23:39:59 +04:00
task = rpc_run_task ( & task_setup_data ) ;
2007-10-16 02:17:53 +04:00
if ( ! IS_ERR ( task ) )
rpc_put_task ( task ) ;
return 1 ;
}
static int nfs_call_unlink ( struct dentry * dentry , struct nfs_unlinkdata * data )
{
2007-07-14 23:39:58 +04:00
struct dentry * parent ;
struct inode * dir ;
2007-10-16 02:17:53 +04:00
int ret = 0 ;
2007-07-14 23:39:58 +04:00
parent = dget_parent ( dentry ) ;
if ( parent = = NULL )
goto out_free ;
2007-10-16 02:17:53 +04:00
dir = parent - > d_inode ;
2007-10-21 20:02:22 +04:00
if ( nfs_copy_dname ( dentry , data ) ! = 0 )
2007-10-16 02:17:53 +04:00
goto out_dput ;
/* Non-exclusive lock protects against concurrent lookup() calls */
spin_lock ( & dir - > i_lock ) ;
if ( atomic_inc_not_zero ( & NFS_I ( dir ) - > silly_count ) = = 0 ) {
/* Deferred delete */
hlist_add_head ( & data - > list , & NFS_I ( dir ) - > silly_list ) ;
spin_unlock ( & dir - > i_lock ) ;
ret = 1 ;
goto out_dput ;
}
spin_unlock ( & dir - > i_lock ) ;
ret = nfs_do_call_unlink ( parent , dir , data ) ;
out_dput :
2007-07-14 23:39:58 +04:00
dput ( parent ) ;
2007-10-16 02:17:53 +04:00
out_free :
return ret ;
}
2007-07-14 23:39:58 +04:00
2007-10-16 02:17:53 +04:00
void nfs_block_sillyrename ( struct dentry * dentry )
{
struct nfs_inode * nfsi = NFS_I ( dentry - > d_inode ) ;
2007-07-14 23:39:58 +04:00
2007-10-16 02:17:53 +04:00
wait_event ( nfsi - > waitqueue , atomic_cmpxchg ( & nfsi - > silly_count , 1 , 0 ) = = 1 ) ;
}
void nfs_unblock_sillyrename ( struct dentry * dentry )
{
struct inode * dir = dentry - > d_inode ;
struct nfs_inode * nfsi = NFS_I ( dir ) ;
struct nfs_unlinkdata * data ;
atomic_inc ( & nfsi - > silly_count ) ;
spin_lock ( & dir - > i_lock ) ;
while ( ! hlist_empty ( & nfsi - > silly_list ) ) {
if ( ! atomic_inc_not_zero ( & nfsi - > silly_count ) )
break ;
data = hlist_entry ( nfsi - > silly_list . first , struct nfs_unlinkdata , list ) ;
hlist_del ( & data - > list ) ;
spin_unlock ( & dir - > i_lock ) ;
if ( nfs_do_call_unlink ( dentry , dir , data ) = = 0 )
nfs_free_unlinkdata ( data ) ;
spin_lock ( & dir - > i_lock ) ;
}
spin_unlock ( & dir - > i_lock ) ;
2007-07-14 23:39:58 +04:00
}
2005-04-17 02:20:36 +04:00
/**
* nfs_async_unlink - asynchronous unlinking of a file
2007-07-14 23:39:58 +04:00
* @ dir : parent directory of dentry
2005-04-17 02:20:36 +04:00
* @ dentry : dentry to unlink
*/
int
2007-07-14 23:39:58 +04:00
nfs_async_unlink ( struct inode * dir , struct dentry * dentry )
2005-04-17 02:20:36 +04:00
{
2007-07-14 23:39:58 +04:00
struct nfs_unlinkdata * data ;
int status = - ENOMEM ;
2005-04-17 02:20:36 +04:00
2006-03-20 21:44:10 +03:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
2007-07-14 23:39:58 +04:00
if ( data = = NULL )
2005-04-17 02:20:36 +04:00
goto out ;
2007-07-14 23:39:58 +04:00
data - > cred = rpcauth_lookupcred ( NFS_CLIENT ( dir ) - > cl_auth , 0 ) ;
2005-04-17 02:20:36 +04:00
if ( IS_ERR ( data - > cred ) ) {
status = PTR_ERR ( data - > cred ) ;
goto out_free ;
}
2007-07-14 23:39:58 +04:00
status = - EBUSY ;
2005-04-17 02:20:36 +04:00
spin_lock ( & dentry - > d_lock ) ;
2007-07-14 23:39:58 +04:00
if ( dentry - > d_flags & DCACHE_NFSFS_RENAMED )
goto out_unlock ;
2005-04-17 02:20:36 +04:00
dentry - > d_flags | = DCACHE_NFSFS_RENAMED ;
2007-07-14 23:39:58 +04:00
dentry - > d_fsdata = data ;
2005-04-17 02:20:36 +04:00
spin_unlock ( & dentry - > d_lock ) ;
2007-07-14 23:39:58 +04:00
return 0 ;
out_unlock :
spin_unlock ( & dentry - > d_lock ) ;
put_rpccred ( data - > cred ) ;
2005-04-17 02:20:36 +04:00
out_free :
kfree ( data ) ;
2007-07-14 23:39:58 +04:00
out :
2005-04-17 02:20:36 +04:00
return status ;
}
/**
* nfs_complete_unlink - Initialize completion of the sillydelete
* @ dentry : dentry to delete
2007-07-14 23:39:58 +04:00
* @ inode : inode
2005-04-17 02:20:36 +04:00
*
* Since we ' re most likely to be called by dentry_iput ( ) , we
* only use the dentry to find the sillydelete . We then copy the name
* into the qstr .
*/
void
2007-07-14 23:39:58 +04:00
nfs_complete_unlink ( struct dentry * dentry , struct inode * inode )
2005-04-17 02:20:36 +04:00
{
2007-07-14 23:39:58 +04:00
struct nfs_unlinkdata * data = NULL ;
2005-04-17 02:20:36 +04:00
spin_lock ( & dentry - > d_lock ) ;
2007-07-14 23:39:58 +04:00
if ( dentry - > d_flags & DCACHE_NFSFS_RENAMED ) {
dentry - > d_flags & = ~ DCACHE_NFSFS_RENAMED ;
data = dentry - > d_fsdata ;
}
2005-04-17 02:20:36 +04:00
spin_unlock ( & dentry - > d_lock ) ;
2007-07-14 23:39:58 +04:00
if ( data ! = NULL & & ( NFS_STALE ( inode ) | | ! nfs_call_unlink ( dentry , data ) ) )
nfs_free_unlinkdata ( data ) ;
2005-04-17 02:20:36 +04:00
}