patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / fs / autofs4 / expire.c
index 4339a52..5ebffa6 100644 (file)
@@ -4,6 +4,7 @@
  *
  *  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
  * ------------------------------------------------------------------------- */
 
 #include "autofs_i.h"
-#include <linux/mount.h>
 
-/*
- * Determine if a subtree of the namespace is busy.
- *
- * mnt is the mount tree under the autofs mountpoint
+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 inline int is_vfsmnt_tree_busy(struct vfsmount *mnt)
+static int autofs4_check_tree(struct vfsmount *mnt,
+                             struct dentry *top,
+                             unsigned long timeout,
+                             int do_now)
 {
-       struct vfsmount *this_parent = mnt;
+       struct dentry *this_parent = top;
        struct list_head *next;
-       int count;
 
-       count = atomic_read(&mnt->mnt_count) - 1;
+       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->mnt_mounts.next;
-       DPRINTK(("is_vfsmnt_tree_busy: mnt=%p, this_parent=%p, next=%p\n",
-                mnt, this_parent, next));
+       next = this_parent->d_subdirs.next;
 resume:
-       for( ; next != &this_parent->mnt_mounts; next = next->next) {
-               struct vfsmount *p = list_entry(next, struct vfsmount,
-                                               mnt_child);
+       while (next != &this_parent->d_subdirs) {
+               struct dentry *dentry = list_entry(next, struct dentry, d_child);
 
-               /* -1 for struct vfs_mount's normal count, 
-                  -1 to compensate for child's reference to parent */
-               count += atomic_read(&p->mnt_count) - 1 - 1;
+               /* Negative dentry - give up */
+               if (!simple_positive(dentry)) {
+                       next = next->next;
+                       continue;
+               }
 
-               DPRINTK(("is_vfsmnt_tree_busy: p=%p, count now %d\n",
-                        p, count));
+               DPRINTK("dentry %p %.*s",
+                       dentry, (int)dentry->d_name.len, dentry->d_name.name);
 
-               if (!list_empty(&p->mnt_mounts)) {
-                       this_parent = p;
+               if (!simple_empty_nolock(dentry)) {
+                       this_parent = dentry;
                        goto repeat;
                }
-               /* root is busy if any leaf is busy */
-               if (atomic_read(&p->mnt_count) > 1)
-                       return 1;
-       }
 
-       /* All done at this level ... ascend and resume the search. */
-       if (this_parent != mnt) {
-               next = this_parent->mnt_child.next; 
-               this_parent = this_parent->mnt_parent;
-               goto resume;
-       }
+               dentry = dget(dentry);
+               spin_unlock(&dcache_lock);
 
-       DPRINTK(("is_vfsmnt_tree_busy: count=%d\n", count));
-       return count != 0; /* remaining users? */
-}
+               if (d_mountpoint(dentry)) {
+                       /* First busy => tree busy */
+                       if (!autofs4_check_mount(mnt, dentry)) {
+                               dput(dentry);
+                               return 0;
+                       }
+               }
 
-/* Traverse a dentry's list of vfsmounts and return the number of
-   non-busy mounts */
-static int check_vfsmnt(struct vfsmount *mnt, struct dentry *dentry)
-{
-       int ret = dentry->d_mounted;
-       struct vfsmount *vfs = lookup_mnt(mnt, dentry);
+               dput(dentry);
+               spin_lock(&dcache_lock);
+               next = next->next;
+       }
 
-       if (vfs) {
-               mntput(vfs);
-               if (is_vfsmnt_tree_busy(vfs))
-                       ret--;
+       if (this_parent != top) {
+               next = this_parent->d_child.next;
+               this_parent = this_parent->d_parent;
+               goto resume;
        }
-       DPRINTK(("check_vfsmnt: ret=%d\n", ret));
-       return ret;
+       spin_unlock(&dcache_lock);
+
+       return 1;
 }
 
-/* Check dentry tree for busyness.  If a dentry appears to be busy
-   because it is a mountpoint, check to see if the mounted
-   filesystem is busy. */
-static int is_tree_busy(struct vfsmount *topmnt, struct dentry *top)
+struct dentry *autofs4_check_leaves(struct vfsmount *mnt,
+                                   struct dentry *parent,
+                                   unsigned long timeout,
+                                   int do_now)
 {
-       struct dentry *this_parent;
+       struct dentry *this_parent = parent;
        struct list_head *next;
-       int count;
 
-       count = atomic_read(&top->d_count);
-       
-       DPRINTK(("is_tree_busy: top=%p initial count=%d\n", 
-                top, count));
-       this_parent = top;
+       DPRINTK("parent %p %.*s",
+               parent, (int)parent->d_name.len, parent->d_name.name);
 
-       if (is_autofs4_dentry(top)) {
-               count--;
-               DPRINTK(("is_tree_busy: autofs; count=%d\n", count));
-       }
-
-       if (d_mountpoint(top))
-               count -= check_vfsmnt(topmnt, top);
-
- repeat:
+       spin_lock(&dcache_lock);
+repeat:
        next = this_parent->d_subdirs.next;
- resume:
+resume:
        while (next != &this_parent->d_subdirs) {
-               int adj = 0;
-               struct dentry *dentry = list_entry(next, struct dentry,
-                                                  d_child);
-               next = next->next;
-
-               count += atomic_read(&dentry->d_count) - 1;
-
-               if (d_mountpoint(dentry))
-                       adj += check_vfsmnt(topmnt, dentry);
+               struct dentry *dentry = list_entry(next, struct dentry, d_child);
 
-               if (is_autofs4_dentry(dentry)) {
-                       adj++;
-                       DPRINTK(("is_tree_busy: autofs; adj=%d\n",
-                                adj));
+               /* Negative dentry - give up */
+               if (!simple_positive(dentry)) {
+                       next = next->next;
+                       continue;
                }
 
-               count -= adj;
+               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;
                }
 
-               if (atomic_read(&dentry->d_count) != adj) {
-                       DPRINTK(("is_tree_busy: busy leaf (d_count=%d adj=%d)\n",
-                                atomic_read(&dentry->d_count), adj));
-                       return 1;
+               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;
        }
 
-       /* All done at this level ... ascend and resume the search. */
-       if (this_parent != top) {
-               next = this_parent->d_child.next; 
+       if (this_parent != parent) {
+               next = this_parent->d_child.next;
                this_parent = this_parent->d_parent;
                goto resume;
        }
+       spin_unlock(&dcache_lock);
 
-       DPRINTK(("is_tree_busy: count=%d\n", count));
-       return count != 0; /* remaining users? */
+       return NULL;
 }
 
 /*
@@ -156,61 +216,86 @@ static int is_tree_busy(struct vfsmount *topmnt, struct dentry *top)
 static struct dentry *autofs4_expire(struct super_block *sb,
                                     struct vfsmount *mnt,
                                     struct autofs_sb_info *sbi,
-                                    int do_now)
+                                    int how)
 {
-       unsigned long now = jiffies;
        unsigned long timeout;
        struct dentry *root = sb->s_root;
-       struct list_head *tmp;
+       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)
+       if ( !sbi->exp_timeout || !root )
                return NULL;
 
+       now = jiffies;
        timeout = sbi->exp_timeout;
 
        spin_lock(&dcache_lock);
-       for(tmp = root->d_subdirs.next;
-           tmp != &root->d_subdirs; 
-           tmp = tmp->next) {
-               struct autofs_info *ino;
-               struct dentry *dentry = list_entry(tmp, struct dentry, d_child);
-
-               if (dentry->d_inode == NULL)
-                       continue;
+       next = root->d_subdirs.next;
 
-               ino = autofs4_dentry_ino(dentry);
+       /* 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);
 
-               if (ino == NULL) {
-                       /* dentry in the process of being deleted */
+               /* Negative dentry - give up */
+               if ( !simple_positive(dentry) ) {
+                       next = next->next;
                        continue;
                }
 
-               /* No point expiring a pending mount */
-               if (dentry->d_flags & DCACHE_AUTOFS_PENDING)
-                       continue;
+               dentry = dget(dentry);
+               spin_unlock(&dcache_lock);
 
-               if (!do_now) {
-                       /* Too young to die */
-                       if (time_after(ino->last_used + timeout, now))
-                               continue;
-               
-                       /* 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;
+               /* 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 (!is_tree_busy(mnt, dentry)) {
-                       DPRINTK(("autofs_expire: returning %p %.*s\n",
-                                dentry, (int)dentry->d_name.len, dentry->d_name.name));
-                       /* Start from here next time */
-                       list_del(&root->d_subdirs);
-                       list_add(&root->d_subdirs, &dentry->d_child);
-                       dget(dentry);
-                       spin_unlock(&dcache_lock);
-
-                       return dentry;
+
+               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);
 
@@ -221,7 +306,7 @@ static struct dentry *autofs4_expire(struct super_block *sb,
 int autofs4_expire_run(struct super_block *sb,
                      struct vfsmount *mnt,
                      struct autofs_sb_info *sbi,
-                     struct autofs_packet_expire *pkt_p)
+                     struct autofs_packet_expire __user *pkt_p)
 {
        struct autofs_packet_expire pkt;
        struct dentry *dentry;
@@ -248,7 +333,7 @@ int autofs4_expire_run(struct super_block *sb,
 /* 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 *arg)
+                       struct autofs_sb_info *sbi, int __user *arg)
 {
        struct dentry *dentry;
        int ret = -EAGAIN;
@@ -263,7 +348,7 @@ int autofs4_expire_multi(struct super_block *sb, struct vfsmount *mnt,
                /* This is synchronous because it makes the daemon a
                    little easier */
                de_info->flags |= AUTOFS_INF_EXPIRING;
-               ret = autofs4_wait(sbi, &dentry->d_name, NFY_EXPIRE);
+               ret = autofs4_wait(sbi, dentry, NFY_EXPIRE);
                de_info->flags &= ~AUTOFS_INF_EXPIRING;
                dput(dentry);
        }