diff options
Diffstat (limited to 'fs/autofs4/expire.c')
-rw-r--r-- | fs/autofs4/expire.c | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/fs/autofs4/expire.c b/fs/autofs4/expire.c new file mode 100644 index 0000000..31540a6 --- /dev/null +++ b/fs/autofs4/expire.c @@ -0,0 +1,358 @@ +/* -*- 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; + + 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) { + if (autofs4_check_tree(mnt, dentry, timeout, do_now)) { + expired = dentry; + break; + } + /* 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; +} + |