2005-04-17 02:20:36 +04:00
/* -*- c -*- --------------------------------------------------------------- *
*
* linux / fs / autofs / expire . c
*
* Copyright 1997 - 1998 Transmeta Corporation - - All Rights Reserved
* Copyright 1999 - 2000 Jeremy Fitzhardinge < jeremy @ goop . org >
* Copyright 2001 - 2003 Ian Kent < raven @ themaw . net >
*
* This file is part of the Linux kernel and is made available under
* the terms of the GNU General Public License , version 2 , or at your
* option , any later version , incorporated herein by reference .
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
# include "autofs_i.h"
static unsigned long now ;
/* Check if a dentry can be expired return 1 if it can else return 0 */
static inline int autofs4_can_expire ( struct dentry * dentry ,
unsigned long timeout , int do_now )
{
struct autofs_info * ino = autofs4_dentry_ino ( dentry ) ;
/* dentry in the process of being deleted */
if ( ino = = NULL )
return 0 ;
/* No point expiring a pending mount */
if ( dentry - > d_flags & DCACHE_AUTOFS_PENDING )
return 0 ;
if ( ! do_now ) {
/* Too young to die */
if ( time_after ( ino - > last_used + timeout , now ) )
return 0 ;
/* update last_used here :-
- obviously makes sense if it is in use now
- less obviously , prevents rapid - fire expire
attempts if expire fails the first time */
ino - > last_used = now ;
}
return 1 ;
}
/* Check a mount point for busyness return 1 if not busy, otherwise */
static int autofs4_check_mount ( struct vfsmount * mnt , struct dentry * dentry )
{
int status = 0 ;
DPRINTK ( " dentry %p %.*s " ,
dentry , ( int ) dentry - > d_name . len , dentry - > d_name . name ) ;
mntget ( mnt ) ;
dget ( dentry ) ;
if ( ! follow_down ( & mnt , & dentry ) )
goto done ;
while ( d_mountpoint ( dentry ) & & follow_down ( & mnt , & dentry ) )
;
/* This is an autofs submount, we can't expire it */
if ( is_autofs4_dentry ( dentry ) )
goto done ;
/* The big question */
if ( may_umount_tree ( mnt ) = = 0 )
status = 1 ;
done :
DPRINTK ( " returning = %d " , status ) ;
mntput ( mnt ) ;
dput ( dentry ) ;
return status ;
}
/* Check a directory tree of mount points for busyness
* The tree is not busy iff no mountpoints are busy
* Return 1 if the tree is busy or 0 otherwise
*/
static int autofs4_check_tree ( struct vfsmount * mnt ,
struct dentry * top ,
unsigned long timeout ,
int do_now )
{
struct dentry * this_parent = top ;
struct list_head * next ;
DPRINTK ( " parent %p %.*s " ,
top , ( int ) top - > d_name . len , top - > d_name . name ) ;
/* Negative dentry - give up */
if ( ! simple_positive ( top ) )
return 0 ;
/* Timeout of a tree mount is determined by its top dentry */
if ( ! autofs4_can_expire ( top , timeout , do_now ) )
return 0 ;
2005-05-01 19:59:17 +04:00
/* Is someone visiting anywhere in the tree ? */
if ( may_umount_tree ( mnt ) )
return 0 ;
2005-04-17 02:20:36 +04:00
spin_lock ( & dcache_lock ) ;
repeat :
next = this_parent - > d_subdirs . next ;
resume :
while ( next ! = & this_parent - > d_subdirs ) {
struct dentry * dentry = list_entry ( next , struct dentry , d_child ) ;
/* Negative dentry - give up */
if ( ! simple_positive ( dentry ) ) {
next = next - > next ;
continue ;
}
DPRINTK ( " dentry %p %.*s " ,
dentry , ( int ) dentry - > d_name . len , dentry - > d_name . name ) ;
if ( ! simple_empty_nolock ( dentry ) ) {
this_parent = dentry ;
goto repeat ;
}
dentry = dget ( dentry ) ;
spin_unlock ( & dcache_lock ) ;
if ( d_mountpoint ( dentry ) ) {
/* First busy => tree busy */
if ( ! autofs4_check_mount ( mnt , dentry ) ) {
dput ( dentry ) ;
return 0 ;
}
}
dput ( dentry ) ;
spin_lock ( & dcache_lock ) ;
next = next - > next ;
}
if ( this_parent ! = top ) {
next = this_parent - > d_child . next ;
this_parent = this_parent - > d_parent ;
goto resume ;
}
spin_unlock ( & dcache_lock ) ;
return 1 ;
}
static struct dentry * autofs4_check_leaves ( struct vfsmount * mnt ,
struct dentry * parent ,
unsigned long timeout ,
int do_now )
{
struct dentry * this_parent = parent ;
struct list_head * next ;
DPRINTK ( " parent %p %.*s " ,
parent , ( int ) parent - > d_name . len , parent - > d_name . name ) ;
spin_lock ( & dcache_lock ) ;
repeat :
next = this_parent - > d_subdirs . next ;
resume :
while ( next ! = & this_parent - > d_subdirs ) {
struct dentry * dentry = list_entry ( next , struct dentry , d_child ) ;
/* Negative dentry - give up */
if ( ! simple_positive ( dentry ) ) {
next = next - > next ;
continue ;
}
DPRINTK ( " dentry %p %.*s " ,
dentry , ( int ) dentry - > d_name . len , dentry - > d_name . name ) ;
if ( ! list_empty ( & dentry - > d_subdirs ) ) {
this_parent = dentry ;
goto repeat ;
}
dentry = dget ( dentry ) ;
spin_unlock ( & dcache_lock ) ;
if ( d_mountpoint ( dentry ) ) {
/* Can we expire this guy */
if ( ! autofs4_can_expire ( dentry , timeout , do_now ) )
goto cont ;
/* Can we umount this guy */
if ( autofs4_check_mount ( mnt , dentry ) )
return dentry ;
}
cont :
dput ( dentry ) ;
spin_lock ( & dcache_lock ) ;
next = next - > next ;
}
if ( this_parent ! = parent ) {
next = this_parent - > d_child . next ;
this_parent = this_parent - > d_parent ;
goto resume ;
}
spin_unlock ( & dcache_lock ) ;
return NULL ;
}
/*
* Find an eligible tree to time - out
* A tree is eligible if : -
* - it is unused by any user process
* - it has been unused for exp_timeout time
*/
static struct dentry * autofs4_expire ( struct super_block * sb ,
struct vfsmount * mnt ,
struct autofs_sb_info * sbi ,
int how )
{
unsigned long timeout ;
struct dentry * root = sb - > s_root ;
struct dentry * expired = NULL ;
struct list_head * next ;
int do_now = how & AUTOFS_EXP_IMMEDIATE ;
int exp_leaves = how & AUTOFS_EXP_LEAVES ;
if ( ! sbi - > exp_timeout | | ! root )
return NULL ;
now = jiffies ;
timeout = sbi - > exp_timeout ;
spin_lock ( & dcache_lock ) ;
next = root - > d_subdirs . next ;
/* On exit from the loop expire is set to a dgot dentry
* to expire or it ' s NULL */
while ( next ! = & root - > d_subdirs ) {
struct dentry * dentry = list_entry ( next , struct dentry , d_child ) ;
/* Negative dentry - give up */
if ( ! simple_positive ( dentry ) ) {
next = next - > next ;
continue ;
}
dentry = dget ( dentry ) ;
spin_unlock ( & dcache_lock ) ;
/* Case 1: indirect mount or top level direct mount */
if ( d_mountpoint ( dentry ) ) {
DPRINTK ( " checking mountpoint %p %.*s " ,
dentry , ( int ) dentry - > d_name . len , dentry - > d_name . name ) ;
/* Can we expire this guy */
if ( ! autofs4_can_expire ( dentry , timeout , do_now ) )
goto next ;
/* Can we umount this guy */
if ( autofs4_check_mount ( mnt , dentry ) ) {
expired = dentry ;
break ;
}
goto next ;
}
if ( simple_empty ( dentry ) )
goto next ;
/* Case 2: tree mount, expire iff entire tree is not busy */
if ( ! exp_leaves ) {
2005-05-01 19:59:17 +04:00
/* Lock the tree as we must expire as a whole */
spin_lock ( & sbi - > fs_lock ) ;
2005-04-17 02:20:36 +04:00
if ( autofs4_check_tree ( mnt , dentry , timeout , do_now ) ) {
2005-05-01 19:59:17 +04:00
struct autofs_info * inf = autofs4_dentry_ino ( dentry ) ;
/* Set this flag early to catch sys_chdir and the like */
inf - > flags | = AUTOFS_INF_EXPIRING ;
spin_unlock ( & sbi - > fs_lock ) ;
expired = dentry ;
break ;
2005-04-17 02:20:36 +04:00
}
2005-05-01 19:59:17 +04:00
spin_unlock ( & sbi - > fs_lock ) ;
2005-04-17 02:20:36 +04:00
/* Case 3: direct mount, expire individual leaves */
} else {
expired = autofs4_check_leaves ( mnt , dentry , timeout , do_now ) ;
if ( expired ) {
dput ( dentry ) ;
break ;
}
}
next :
dput ( dentry ) ;
spin_lock ( & dcache_lock ) ;
next = next - > next ;
}
if ( expired ) {
DPRINTK ( " returning %p %.*s " ,
expired , ( int ) expired - > d_name . len , expired - > d_name . name ) ;
spin_lock ( & dcache_lock ) ;
list_del ( & expired - > d_parent - > d_subdirs ) ;
list_add ( & expired - > d_parent - > d_subdirs , & expired - > d_child ) ;
spin_unlock ( & dcache_lock ) ;
return expired ;
}
spin_unlock ( & dcache_lock ) ;
return NULL ;
}
/* Perform an expiry operation */
int autofs4_expire_run ( struct super_block * sb ,
struct vfsmount * mnt ,
struct autofs_sb_info * sbi ,
struct autofs_packet_expire __user * pkt_p )
{
struct autofs_packet_expire pkt ;
struct dentry * dentry ;
memset ( & pkt , 0 , sizeof pkt ) ;
pkt . hdr . proto_version = sbi - > version ;
pkt . hdr . type = autofs_ptype_expire ;
if ( ( dentry = autofs4_expire ( sb , mnt , sbi , 0 ) ) = = NULL )
return - EAGAIN ;
pkt . len = dentry - > d_name . len ;
memcpy ( pkt . name , dentry - > d_name . name , pkt . len ) ;
pkt . name [ pkt . len ] = ' \0 ' ;
dput ( dentry ) ;
if ( copy_to_user ( pkt_p , & pkt , sizeof ( struct autofs_packet_expire ) ) )
return - EFAULT ;
return 0 ;
}
/* Call repeatedly until it returns -EAGAIN, meaning there's nothing
more to be done */
int autofs4_expire_multi ( struct super_block * sb , struct vfsmount * mnt ,
struct autofs_sb_info * sbi , int __user * arg )
{
struct dentry * dentry ;
int ret = - EAGAIN ;
int do_now = 0 ;
if ( arg & & get_user ( do_now , arg ) )
return - EFAULT ;
if ( ( dentry = autofs4_expire ( sb , mnt , sbi , do_now ) ) ! = NULL ) {
struct autofs_info * de_info = autofs4_dentry_ino ( dentry ) ;
/* This is synchronous because it makes the daemon a
little easier */
de_info - > flags | = AUTOFS_INF_EXPIRING ;
ret = autofs4_wait ( sbi , dentry , NFY_EXPIRE ) ;
de_info - > flags & = ~ AUTOFS_INF_EXPIRING ;
dput ( dentry ) ;
}
return ret ;
}