Folks, give it a try. Aside of the bunch of changes in fs/super.c
(first batch of the fs/super.c changes from mulit-mount patch) there are
        a) VFS handling of the removed-but-still-opened directories. ext2
and ufs already knew how to deal with them, now almost all filesystems
handle them too. BTW, some (ramfs, for one) did not handle it correctly.
Fixed.
        b) last methods (rename()/rmdir()/unlink()) switched to knew
race-free lookup logics. Cases to watch for: names ending with "." or ".."
in rmdir() and rename(), attempts to rename()/rmdir() the root of chroot
environment.
        c) fs/nfsd/export.c somewhat cleaned up, ditto for autofs and
autofs4. They should work as they used to, but extra check doesn't hurt...

Patch attached.
                                                        Cheers,
                                                                Al
diff -urN linux-2.3.99-pre6-5/fs/autofs/autofs_i.h 
linux-bird.vfs-1/fs/autofs/autofs_i.h
--- linux-2.3.99-pre6-5/fs/autofs/autofs_i.h    Sun Apr  2 14:32:50 2000
+++ linux-bird.vfs-1/fs/autofs/autofs_i.h       Mon Apr 24 19:04:54 2000
@@ -56,13 +56,12 @@
        struct autofs_dir_ent **back;
        /* The following entries are for the expiry system */
        unsigned long last_usage;
-       struct autofs_dir_ent *exp_next;
-       struct autofs_dir_ent *exp_prev;
+       struct list_head exp;
 };
 
 struct autofs_dirhash {
        struct autofs_dir_ent *h[AUTOFS_HASH_SIZE];
-       struct autofs_dir_ent expiry_head;
+       struct list_head expiry_head;
 };
 
 struct autofs_wait_queue {
diff -urN linux-2.3.99-pre6-5/fs/autofs/dirhash.c linux-bird.vfs-1/fs/autofs/dirhash.c
--- linux-2.3.99-pre6-5/fs/autofs/dirhash.c     Tue Apr 11 18:09:48 2000
+++ linux-bird.vfs-1/fs/autofs/dirhash.c        Mon Apr 24 18:55:44 2000
@@ -17,17 +17,13 @@
 static void autofs_init_usage(struct autofs_dirhash *dh,
                              struct autofs_dir_ent *ent)
 {
-       ent->exp_next = &dh->expiry_head;
-       ent->exp_prev = dh->expiry_head.exp_prev;
-       dh->expiry_head.exp_prev->exp_next = ent;
-       dh->expiry_head.exp_prev = ent;
+       list_add_tail(&ent->exp, &dh->expiry_head);
        ent->last_usage = jiffies;
 }
 
 static void autofs_delete_usage(struct autofs_dir_ent *ent)
 {
-       ent->exp_prev->exp_next = ent->exp_next;
-       ent->exp_next->exp_prev = ent->exp_prev;
+       list_del(&ent->exp);
 }
 
 void autofs_update_usage(struct autofs_dirhash *dh,
@@ -45,12 +41,13 @@
        struct dentry *dentry;
        unsigned long timeout = sbi->exp_timeout;
 
-       ent = dh->expiry_head.exp_next;
-
-       if ( ent == &(dh->expiry_head) || sbi->catatonic )
-               return NULL;    /* No entries */
-
-       while ( jiffies - ent->last_usage >= timeout ) {
+       while (1) {
+               if ( list_empty(&dh->expiry_head) || sbi->catatonic )
+                       return NULL;    /* No entries */
+               /* We keep the list sorted by last_usage and want old stuff */
+               ent = list_entry(dh->expiry_head.next, struct autofs_dir_ent, exp);
+               if (jiffies - ent->last_usage < timeout)
+                       break;
                /* Move to end of list in case expiry isn't desirable */
                autofs_update_usage(dh, ent);
 
@@ -94,8 +91,7 @@
 
 void autofs_initialize_hash(struct autofs_dirhash *dh) {
        memset(&dh->h, 0, AUTOFS_HASH_SIZE*sizeof(struct autofs_dir_ent *));
-       dh->expiry_head.exp_next = dh->expiry_head.exp_prev =
-               &dh->expiry_head;
+       INIT_LIST_HEAD(&dh->expiry_head);
 }
 
 struct autofs_dir_ent *autofs_hash_lookup(const struct autofs_dirhash *dh, struct 
qstr *name)
diff -urN linux-2.3.99-pre6-5/fs/autofs/inode.c linux-bird.vfs-1/fs/autofs/inode.c
--- linux-2.3.99-pre6-5/fs/autofs/inode.c       Thu Mar 16 14:20:33 2000
+++ linux-bird.vfs-1/fs/autofs/inode.c  Mon Apr 24 18:55:44 2000
@@ -19,19 +19,6 @@
 #define __NO_VERSION__
 #include <linux/module.h>
 
-/*
- * Dummy functions - do we ever actually want to do
- * something here?
- */
-static void autofs_put_inode(struct inode *inode)
-{
-}
-
-static void autofs_delete_inode(struct inode *inode)
-{
-       inode->i_size = 0;
-}
-
 static void autofs_put_super(struct super_block *sb)
 {
        struct autofs_sb_info *sbi = autofs_sbi(sb);
@@ -53,13 +40,9 @@
 
 static int autofs_statfs(struct super_block *sb, struct statfs *buf);
 static void autofs_read_inode(struct inode *inode);
-static void autofs_write_inode(struct inode *inode);
 
 static struct super_operations autofs_sops = {
        read_inode:     autofs_read_inode,
-       write_inode:    autofs_write_inode,
-       put_inode:      autofs_put_inode,
-       delete_inode:   autofs_delete_inode,
        put_super:      autofs_put_super,
        statfs:         autofs_statfs,
 };
@@ -137,10 +120,6 @@
        struct autofs_sb_info *sbi;
        int minproto, maxproto;
 
-       /* Super block already completed? */
-       if (s->s_root)
-               goto out_unlock;
-
        sbi = (struct autofs_sb_info *) kmalloc(sizeof(struct autofs_sb_info), 
GFP_KERNEL);
        if ( !sbi )
                goto fail_unlock;
@@ -159,25 +138,15 @@
        s->s_blocksize_bits = 10;
        s->s_magic = AUTOFS_SUPER_MAGIC;
        s->s_op = &autofs_sops;
-       s->s_root = NULL;
 
-       /*
-        * Get the root inode and dentry, but defer checking for errors.
-        */
        root_inode = iget(s, AUTOFS_ROOT_INO);
        root = d_alloc_root(root_inode);
        pipe = NULL;
 
-       /*
-        * Check whether somebody else completed the super block.
-        */
-       if (s->s_root)
-               goto out_dput;
-
        if (!root)
                goto fail_iput;
 
-       /* Can this call block? */
+       /* Can this call block?  - WTF cares? s is locked. */
        if ( 
parse_options(data,&pipefd,&root_inode->i_uid,&root_inode->i_gid,&sbi->oz_pgrp,&minproto,&maxproto)
 ) {
                printk("autofs: called with bogus options\n");
                goto fail_dput;
@@ -192,11 +161,6 @@
 
        DPRINTK(("autofs: pipe fd = %d, pgrp = %u\n", pipefd, sbi->oz_pgrp));
        pipe = fget(pipefd);
-       /*
-        * Check whether somebody else completed the super block.
-        */
-       if (s->s_root)
-               goto out_fput;
        
        if ( !pipe ) {
                printk("autofs: could not open pipe file descriptor\n");
@@ -212,43 +176,14 @@
        s->s_root = root;
        return s;
 
-       /*
-        * Success ... somebody else completed the super block for us. 
-        */ 
-out_unlock:
-       goto out_dec;
-out_fput:
-       if (pipe)
-               fput(pipe);
-out_dput:
-       if (root)
-               dput(root);
-       else
-               iput(root_inode);
-out_dec:
-       return s;
-       
-       /*
-        * Failure ... clear the s_dev slot and clean up.
-        */
 fail_fput:
        printk("autofs: pipe file descriptor does not contain proper ops\n");
-       /*
-        * fput() can block, so we clear the super block first.
-        */
        fput(pipe);
-       /* fall through */
 fail_dput:
-       /*
-        * dput() can block, so we clear the super block first.
-        */
        dput(root);
        goto fail_free;
 fail_iput:
        printk("autofs: get root dentry failed\n");
-       /*
-        * iput() can block, so we clear the super block first.
-        */
        iput(root_inode);
 fail_free:
        kfree(sbi);
@@ -260,9 +195,6 @@
 {
        buf->f_type = AUTOFS_SUPER_MAGIC;
        buf->f_bsize = 1024;
-       buf->f_bfree = 0;
-       buf->f_bavail = 0;
-       buf->f_ffree = 0;
        buf->f_namelen = NAME_MAX;
        return 0;
 }
@@ -313,8 +245,4 @@
                inode->i_size = sl->len;
                inode->i_nlink = 1;
        }
-}
-
-static void autofs_write_inode(struct inode *inode)
-{
 }
diff -urN linux-2.3.99-pre6-5/fs/autofs/root.c linux-bird.vfs-1/fs/autofs/root.c
--- linux-2.3.99-pre6-5/fs/autofs/root.c        Tue Apr 11 18:09:48 2000
+++ linux-bird.vfs-1/fs/autofs/root.c   Mon Apr 24 18:55:44 2000
@@ -186,7 +186,7 @@
        autofs_say(dentry->d_name.name,dentry->d_name.len);
 
        if (dentry->d_name.len > NAME_MAX)
-               return ERR_PTR(-ENOENT);/* File name too long to exist */
+               return ERR_PTR(-ENAMETOOLONG);/* File name too long to exist */
 
        sbi = autofs_sbi(dir->i_sb);
 
@@ -248,9 +248,6 @@
        if ( !autofs_oz_mode(sbi) )
                return -EACCES;
 
-       if ( dentry->d_name.len > NAME_MAX )
-               return -ENAMETOOLONG;
-
        if ( autofs_hash_lookup(dh, &dentry->d_name) )
                return -EEXIST;
 
@@ -374,9 +371,6 @@
 
        if ( !autofs_oz_mode(sbi) )
                return -EACCES;
-
-       if ( dentry->d_name.len > NAME_MAX )
-               return -ENAMETOOLONG;
 
        ent = autofs_hash_lookup(dh, &dentry->d_name);
        if ( ent )
diff -urN linux-2.3.99-pre6-5/fs/autofs4/inode.c linux-bird.vfs-1/fs/autofs4/inode.c
--- linux-2.3.99-pre6-5/fs/autofs4/inode.c      Fri Apr 21 21:58:56 2000
+++ linux-bird.vfs-1/fs/autofs4/inode.c Mon Apr 24 18:55:44 2000
@@ -183,10 +183,6 @@
        struct autofs_sb_info *sbi;
        int minproto, maxproto;
 
-       /* Super block already completed? */
-       if (s->s_root)
-               goto out_unlock;
-
        sbi = (struct autofs_sb_info *) kmalloc(sizeof(*sbi), GFP_KERNEL);
        if ( !sbi )
                goto fail_unlock;
@@ -206,7 +202,6 @@
        s->s_blocksize_bits = 10;
        s->s_magic = AUTOFS_SUPER_MAGIC;
        s->s_op = &autofs4_sops;
-       s->s_root = NULL;
 
        /*
         * Get the root inode and dentry, but defer checking for errors.
@@ -217,12 +212,6 @@
        root = d_alloc_root(root_inode);
        pipe = NULL;
 
-       /*
-        * Check whether somebody else completed the super block.
-        */
-       if (s->s_root)
-               goto out_dput;
-
        if (!root)
                goto fail_iput;
 
@@ -249,11 +238,6 @@
 
        DPRINTK(("autofs: pipe fd = %d, pgrp = %u\n", pipefd, sbi->oz_pgrp));
        pipe = fget(pipefd);
-       /*
-        * Check whether somebody else completed the super block.
-        */
-       if (s->s_root)
-               goto out_fput;
        
        if ( !pipe ) {
                printk("autofs: could not open pipe file descriptor\n");
@@ -268,25 +252,9 @@
         */
        s->s_root = root;
        return s;
-
-       /*
-        * Success ... somebody else completed the super block for us. 
-        */ 
-out_unlock:
-       goto out_dec;
-out_fput:
-       if (pipe)
-               fput(pipe);
-out_dput:
-       if (root)
-               dput(root);
-       else
-               iput(root_inode);
-out_dec:
-       return s;
        
        /*
-        * Failure ... clear the s_dev slot and clean up.
+        * Failure ... clean up.
         */
 fail_fput:
        printk("autofs: pipe file descriptor does not contain proper ops\n");
@@ -317,9 +285,6 @@
 {
        buf->f_type = AUTOFS_SUPER_MAGIC;
        buf->f_bsize = 1024;
-       buf->f_bfree = 0;
-       buf->f_bavail = 0;
-       buf->f_ffree = 0;
        buf->f_namelen = NAME_MAX;
        return 0;
 }
diff -urN linux-2.3.99-pre6-5/fs/autofs4/root.c linux-bird.vfs-1/fs/autofs4/root.c
--- linux-2.3.99-pre6-5/fs/autofs4/root.c       Fri Apr 21 21:58:57 2000
+++ linux-bird.vfs-1/fs/autofs4/root.c  Mon Apr 24 18:55:46 2000
@@ -87,7 +87,7 @@
                filp->f_pos = ++nr;
                /* fall through */
        case 1:
-               if (filldir(dirent, "..", 2, nr, 
dentry->d_covers->d_parent->d_inode->i_ino) < 0)
+               if (filldir(dirent, "..", 2, nr, dentry->d_parent->d_inode->i_ino) < 
+0)
                        return 0;
                filp->f_pos = ++nr;
                /* fall through */
@@ -305,7 +305,7 @@
                 dentry->d_name.len, dentry->d_name.name));
 
        if (dentry->d_name.len > NAME_MAX)
-               return ERR_PTR(-ENOENT);/* File name too long to exist */
+               return ERR_PTR(-ENAMETOOLONG);/* File name too long to exist */
 
        sbi = autofs4_sbi(dir->i_sb);
 
@@ -369,18 +369,9 @@
        DPRINTK(("autofs_dir_symlink: %s <- %.*s\n", symname, 
                 dentry->d_name.len, dentry->d_name.name));
 
-       if (!S_ISDIR(dir->i_mode))
-               return -ENOTDIR;
-
        if (!autofs4_oz_mode(sbi))
                return -EACCES;
 
-       if (dentry->d_name.len > NAME_MAX)
-               return -ENAMETOOLONG;
-
-       if (dentry->d_inode != NULL)
-               return -EEXIST;
-
        ino = autofs4_init_ino(ino, sbi, S_IFLNK | 0555);
        if (ino == NULL)
                return -ENOSPC;
@@ -431,12 +422,6 @@
 {
        struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
        struct autofs_info *ino = autofs4_dentry_ino(dentry);
-
-       if (!S_ISDIR(dir->i_mode))
-               return -ENOTDIR;
-
-       if (dentry->d_inode == NULL)
-               return -ENOENT;
        
        /* This allows root to remove symlinks */
        if ( !autofs4_oz_mode(sbi) && !capable(CAP_SYS_ADMIN) )
@@ -461,12 +446,6 @@
 {
        struct autofs_sb_info *sbi = autofs4_sbi(dir->i_sb);
        struct autofs_info *ino = autofs4_dentry_ino(dentry);
-
-       if (!S_ISDIR(dir->i_mode))
-               return -ENOTDIR;
-
-       if (dentry->d_inode == NULL)
-               return -ENOENT;
        
        if (!autofs4_oz_mode(sbi))
                return -EACCES;
@@ -498,17 +477,8 @@
        struct autofs_info *ino = autofs4_dentry_ino(dentry);
        struct inode *inode;
 
-       if (!S_ISDIR(dir->i_mode))
-               return -ENOTDIR;
-
        if ( !autofs4_oz_mode(sbi) )
                return -EACCES;
-
-       if ( dentry->d_inode != NULL )
-               return -EEXIST;
-       
-       if ( dentry->d_name.len > NAME_MAX )
-               return -ENAMETOOLONG;
 
        DPRINTK(("autofs_dir_mkdir: dentry %p, creating %.*s\n",
                 dentry, dentry->d_name.len, dentry->d_name.name));
diff -urN linux-2.3.99-pre6-5/fs/ext2/namei.c linux-bird.vfs-1/fs/ext2/namei.c
--- linux-2.3.99-pre6-5/fs/ext2/namei.c Fri Apr 21 21:58:58 2000
+++ linux-bird.vfs-1/fs/ext2/namei.c    Mon Apr 24 18:55:46 2000
@@ -218,19 +218,10 @@
        struct super_block * sb;
        int     retval;
 
-       if (!dir || !dir->i_nlink)
-               return -EINVAL;
        sb = dir->i_sb;
 
        if (!namelen)
                return -EINVAL;
-       /*
-        * Is this a busy deleted directory?  Can't create new files if so
-        */
-       if (dir->i_size == 0)
-       {
-               return -ENOENT;
-       }
        bh = ext2_bread (dir, 0, 0, &retval);
        if (!bh)
                return retval;
diff -urN linux-2.3.99-pre6-5/fs/hpfs/namei.c linux-bird.vfs-1/fs/hpfs/namei.c
--- linux-2.3.99-pre6-5/fs/hpfs/namei.c Wed Mar  8 12:04:09 2000
+++ linux-bird.vfs-1/fs/hpfs/namei.c    Mon Apr 24 18:55:46 2000
@@ -375,11 +375,6 @@
                hpfs_unlock_2inodes(dir, inode);
                return -ENOTDIR;
        }
-       if (!d_unhashed(dentry)) {
-               hpfs_brelse4(&qbh);
-               hpfs_unlock_2inodes(dir, inode);
-               return -EBUSY;
-       }
        hpfs_count_dnodes(dir->i_sb, inode->i_hpfs_dno, NULL, NULL, &n_items);
        if (n_items) {
                hpfs_brelse4(&qbh);
diff -urN linux-2.3.99-pre6-5/fs/minix/namei.c linux-bird.vfs-1/fs/minix/namei.c
--- linux-2.3.99-pre6-5/fs/minix/namei.c        Wed Mar  8 12:04:09 2000
+++ linux-bird.vfs-1/fs/minix/namei.c   Mon Apr 24 18:55:47 2000
@@ -399,10 +399,6 @@
                retval = -ENOENT;
                goto end_rmdir;
        }
-       if (!d_unhashed(dentry)) {
-               retval = -EBUSY;
-               goto end_rmdir;
-       }
        if (inode->i_nlink != 2)
                printk("empty directory has nlink!=2 (%d)\n",inode->i_nlink);
        de->inode = 0;
@@ -569,9 +565,6 @@
        }
        if (S_ISDIR(old_inode->i_mode)) {
                if (new_inode) {
-                       retval = -EBUSY;
-                       if (!d_unhashed(new_dentry))
-                               goto end_rename;
                        retval = -ENOTEMPTY;
                        if (!empty_dir(new_inode))
                                goto end_rename;
diff -urN linux-2.3.99-pre6-5/fs/msdos/namei.c linux-bird.vfs-1/fs/msdos/namei.c
--- linux-2.3.99-pre6-5/fs/msdos/namei.c        Mon Mar 13 15:35:39 2000
+++ linux-bird.vfs-1/fs/msdos/namei.c   Mon Apr 24 18:55:47 2000
@@ -325,9 +325,6 @@
         * Check whether the directory is not in use, then check
         * whether it is empty.
         */
-       res = -EBUSY;
-       if (!d_unhashed(dentry))
-               goto rmdir_done;
        res = fat_dir_empty(inode);
        if (res)
                goto rmdir_done;
@@ -463,9 +460,6 @@
                goto degenerate_case;
        if (is_dir) {
                if (new_inode) {
-                       error = -EBUSY;
-                       if (!d_unhashed(new_dentry))
-                               goto out;
                        error = fat_dir_empty(new_inode);
                        if (error)
                                goto out;
diff -urN linux-2.3.99-pre6-5/fs/namei.c linux-bird.vfs-1/fs/namei.c
--- linux-2.3.99-pre6-5/fs/namei.c      Fri Apr 21 21:58:58 2000
+++ linux-bird.vfs-1/fs/namei.c Mon Apr 24 19:09:18 2000
@@ -462,14 +462,18 @@
                goto return_base;
 no_inode:
                err = -ENOENT;
-               if (lookup_flags & LOOKUP_POSITIVE)
+               if (lookup_flags & (LOOKUP_POSITIVE|LOOKUP_DIRECTORY))
                        break;
-               if (lookup_flags & LOOKUP_DIRECTORY)
-                       if (!(lookup_flags & LOOKUP_SLASHOK))
-                               break;
                goto return_base;
 lookup_parent:
                nd->last = this;
+               nd->last_type = LAST_NORM;
+               if (this.name[0] != '.')
+                       goto return_base;
+               if (this.len == 1)
+                       nd->last_type = LAST_DOT;
+               else if (this.len == 2 && this.name[1] == '.')
+                       nd->last_type = LAST_DOTDOT;
 return_base:
                return 0;
 out_dput:
@@ -492,7 +496,7 @@
 
        if (!nd->dentry->d_inode) {
                struct nameidata nd_root;
-               nd_root.last.len = 0;
+               nd_root.last_type = LAST_ROOT;
                nd_root.flags = nd->flags;
                nd_root.mnt = mntget(current->fs->rootmnt);
                nd_root.dentry = dget(current->fs->root);
@@ -550,7 +554,7 @@
 
 int walk_init(const char *name,unsigned int flags,struct nameidata *nd)
 {
-       nd->last.len = 0;
+       nd->last_type = LAST_ROOT; /* if there are only slashes... */
        nd->flags = flags;
        if (*name=='/')
                return walk_init_root(name,nd);
@@ -704,6 +708,8 @@
        int error;
        if (!victim->d_inode || victim->d_parent->d_inode != dir)
                return -ENOENT;
+       if (IS_DEADDIR(dir))
+               return -ENOENT;
        error = permission(dir,MAY_WRITE | MAY_EXEC);
        if (error)
                return error;
@@ -735,6 +741,8 @@
 static inline int may_create(struct inode *dir, struct dentry *child) {
        if (child->d_inode)
                return -EEXIST;
+       if (IS_DEADDIR(dir))
+               return -ENOENT;
        return permission(dir,MAY_WRITE | MAY_EXEC);
 }
 
@@ -825,9 +833,7 @@
                 * luserdom and let him sod off - -EISDIR it is.
                 */
                error = -EISDIR;
-               if (!nd->last.len || (nd->last.name[0] == '.' &&
-                    (nd->last.len == 1 ||
-                     (nd->last.name[1] == '.' && nd->last.len == 2))))
+               if (nd->last_type != LAST_NORM)
                        goto exit;
                /* same for foo/ */
                if (nd->last.name[nd->last.len])
@@ -971,8 +977,7 @@
                goto out;
        down(&nd.dentry->d_inode->i_sem);
        dentry = ERR_PTR(-EEXIST);
-       if (!nd.last.len || (nd.last.name[0] == '.' &&
-             (nd.last.len == 1 || (nd.last.name[1] == '.' && nd.last.len == 2))))
+       if (nd.last_type != LAST_NORM)
                goto fail;
        dentry = lookup_hash(&nd.last, nd.dentry);
        if (IS_ERR(dentry))
@@ -1172,48 +1177,89 @@
        double_down(&dir->i_zombie, &dentry->d_inode->i_zombie);
        d_unhash(dentry);
        error = dir->i_op->rmdir(dir, dentry);
+       if (!error)
+               dentry->d_inode->i_flags |= S_DEAD;
        double_up(&dir->i_zombie, &dentry->d_inode->i_zombie);
        dput(dentry);
 
        return error;
 }
 
-static inline int do_rmdir(const char * name)
+static int moving_root(struct dentry *dentry, struct vfsmount *mnt)
 {
-       int error;
-       struct dentry *dir;
-       struct dentry *dentry;
-
-       dentry = lookup_dentry(name, LOOKUP_POSITIVE);
-       error = PTR_ERR(dentry);
-       if (IS_ERR(dentry))
-               goto exit;
-
-       dir = lock_parent(dentry);
-       error = -ENOENT;
-       if (check_parent(dir, dentry))
-               error = vfs_rmdir(dir->d_inode, dentry);
-       unlock_dir(dir);
-       dput(dentry);
-exit:
+       int error = -EBUSY;
+       if (dentry == current->fs->root &&
+           mnt == current->fs->rootmnt)
+               goto out;
+       /* MOUNT_REWRITE: we'll need that
+       if (dentry == mnt->mnt_root)
+               goto out;
+       */
+       error = 0;
+out:
        return error;
 }
 
 asmlinkage long sys_rmdir(const char * pathname)
 {
-       int error;
-       char * tmp;
+       int error = 0;
+       char * name;
+       struct dentry *dir;
+       struct dentry *dentry;
+       struct nameidata nd;
 
-       tmp = getname(pathname);
-       if(IS_ERR(tmp))
-               return PTR_ERR(tmp);
+       name = getname(pathname);
+       if(IS_ERR(name))
+               return PTR_ERR(name);
        lock_kernel();
-       error = do_rmdir(tmp);
-       unlock_kernel();
 
-       putname(tmp);
+retry:
+       if (walk_init(name, LOOKUP_PARENT, &nd))
+               error = walk_name(name, &nd);
+       if (error)
+               goto exit;
 
+       error = -EBUSY;
+       dir = nd.dentry;
+       switch(nd.last_type) {
+               case LAST_DOTDOT:
+                       error = -ENOTEMPTY;
+               case LAST_ROOT:
+                       goto exit1;
+               case LAST_DOT:
+                       if (moving_root(nd.dentry,nd.mnt))
+                               goto exit1;
+                       dir = dir->d_parent;
+                       break;
+       }
+       dget(dir);
+       down(&dir->d_inode->i_sem);
+       if (nd.last_type == LAST_NORM) {
+               dentry = lookup_hash(&nd.last, nd.dentry);
+               error = PTR_ERR(dentry);
+               if (IS_ERR(dentry))
+                       goto exit2;
+       } else if (d_unhashed(dentry = nd.dentry))
+               goto cleanup_and_retry;
+               
+       error = vfs_rmdir(dir->d_inode, dentry);
+exit2:
+       unlock_dir(dir);
+       if (nd.last_type == LAST_NORM)
+               dput(dentry);
+exit1:
+       dput(nd.dentry);
+       mntput(nd.mnt);
+exit:
+       unlock_kernel();
+       putname(name);
        return error;
+
+cleanup_and_retry:
+       unlock_dir(dir);
+       dput(nd.dentry);
+       mntput(nd.mnt);
+       goto retry;
 }
 
 int vfs_unlink(struct inode *dir, struct dentry *dentry)
@@ -1233,42 +1279,55 @@
        return error;
 }
 
-static int do_unlink(const char * name)
+asmlinkage long sys_unlink(const char * pathname)
 {
-       int error;
+       int error = 0;
+       char * name;
        struct dentry *dir;
        struct dentry *dentry;
+       struct nameidata nd;
+
+       name = getname(pathname);
+       if(IS_ERR(name))
+               return PTR_ERR(name);
+       lock_kernel();
 
-       dentry = lookup_dentry(name, LOOKUP_POSITIVE);
+       if (walk_init(name, LOOKUP_PARENT, &nd))
+               error = walk_name(name, &nd);
+       if (error)
+               goto exit;
+       error = -EISDIR;
+       if (nd.last_type != LAST_NORM)
+               goto exit1;
+       dir = dget(nd.dentry);
+       down(&dir->d_inode->i_sem);
+       dentry = lookup_hash(&nd.last, dir);
        error = PTR_ERR(dentry);
        if (IS_ERR(dentry))
-               goto exit;
-
-       dir = lock_parent(dentry);
-       error = -ENOENT;
-       if (check_parent(dir, dentry))
-               error = vfs_unlink(dir->d_inode, dentry);
-
+               goto fail;
+       /* Why not before? Because we want correct error value */
+       if (nd.last.name[nd.last.len])
+               goto slashes;
+       error = vfs_unlink(dir->d_inode, dentry);
+exit2:
         unlock_dir(dir);
        dput(dentry);
+exit1:
+       dput(nd.dentry);
+       mntput(nd.mnt);
 exit:
-       return error;
-}
-
-asmlinkage long sys_unlink(const char * pathname)
-{
-       int error;
-       char * tmp;
-
-       tmp = getname(pathname);
-       if(IS_ERR(tmp))
-               return PTR_ERR(tmp);
-       lock_kernel();
-       error = do_unlink(tmp);
        unlock_kernel();
-       putname(tmp);
+       putname(name);
 
        return error;
+
+slashes:
+       error = !dentry->d_inode ? -ENOENT :
+               S_ISDIR(dentry->d_inode->i_mode) ? -EISDIR : -ENOTDIR;
+       goto exit2;
+fail:
+       unlock_dir(dir);
+       goto exit;
 }
 
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
@@ -1485,6 +1544,8 @@
                            &new_dir->i_zombie);
        error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
        if (target) {
+               if (!error)
+                       target->i_flags |= S_DEAD;
                triple_up(&old_dir->i_zombie,
                          &new_dir->i_zombie,
                          &target->i_zombie);
@@ -1549,45 +1610,165 @@
                return vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry);
 }
 
+/*
+ * To make the things worse, we allow even more perversions than BSD does.
+ *     a) We allow the source to end with ".". Semantics: move the (only) link
+ *        to that directory.
+ *     b) We allow the source to end with "..". Semantics: move the (only)
+ *        link to that directory.
+ *     c) We allow the target to end with ".". Semantics: move the source to
+ *        the (only) link to that directory.
+ *     Combinations are possible too. To put it very mildly, it sucks. Other
+ *     Unices refuse to do that for a very good reason - potential for races
+ *     is insane. Solution? Retry the lookups. Yes, Virginia, it sucks
+ *     putrefied squirrels through the straw. To add a lovely touch to the
+ *     whole pile, we can't do blind lookups and hope that retry logics
+ *     will catch all nasties - for normal cases (link renaming) we _must_
+ *     lock the parents before the last steps of lookups.
+ *
+ * Linus, could we lose that, erm, feature? Please? We are overloading the
+ * semantics of the operation that is already badly overloaded. After all,
+ * Linux is not GNU, NT or VMS...
+ */
+
 static inline int do_rename(const char * oldname, const char * newname)
 {
-       int error;
+       int error = 0;
        struct dentry * old_dir, * new_dir;
        struct dentry * old_dentry, *new_dentry;
+       struct nameidata oldnd, newnd;
+       int is_dir;
 
-       old_dentry = lookup_dentry(oldname, LOOKUP_POSITIVE);
+retry:
+       if (walk_init(oldname, LOOKUP_PARENT, &oldnd))
+               error = walk_name(oldname, &oldnd);
 
-       error = PTR_ERR(old_dentry);
-       if (IS_ERR(old_dentry))
+       if (error)
                goto exit;
 
-       {
-               unsigned int flags = 0;
-               if (S_ISDIR(old_dentry->d_inode->i_mode))
-                       flags = LOOKUP_SLASHOK;
-               new_dentry = lookup_dentry(newname, flags);
-       }
+       if (walk_init(newname, LOOKUP_PARENT, &newnd))
+               error = walk_name(newname, &newnd);
+       if (error)
+               goto exit1;
 
-       error = PTR_ERR(new_dentry);
-       if (IS_ERR(new_dentry))
-               goto exit_old;
+       error = -EXDEV;
+       if (oldnd.mnt != newnd.mnt)
+               goto exit2;
 
-       new_dir = get_parent(new_dentry);
-       old_dir = get_parent(old_dentry);
+       /* Check other perversions */
+       old_dir = oldnd.dentry;
+       error = -EBUSY;
+       switch (oldnd.last_type) {
+               case LAST_ROOT:
+                       goto exit2;
+               case LAST_DOTDOT:
+                       error = moving_root(old_dir, oldnd.mnt);
+                       if (error)
+                               goto exit2;
+                       old_dir = old_dir->d_parent;
+                       /* fallthru */
+               case LAST_DOT:
+                       error = moving_root(old_dir, oldnd.mnt);
+                       if (error)
+                               goto exit2;
+                       old_dir = old_dir->d_parent;
+       }
+
+       new_dir = newnd.dentry;
+       switch (newnd.last_type) {
+               case LAST_DOT:
+                       error = moving_root(new_dir, newnd.mnt);
+                       if (error)
+                               goto exit2;
+                       new_dir = new_dir->d_parent;
+                       break;
+               case LAST_DOTDOT:
+                       error = -ENOTEMPTY;
+               case LAST_ROOT:
+                       goto exit2;
+       }
 
+       /* Uff... We got the parents and they look sane. */
+       dget(old_dir);
+       dget(new_dir);
        double_lock(new_dir, old_dir);
+       /*
+        * Get them. Additional lovely piece of ugliness comes from the
+        * handling of trailing slashes - if source is not a directory
+        * they should give -ENOTDIR.
+        */
+       switch(oldnd.last_type) {
+               default:
+                       old_dentry = lookup_hash(&oldnd.last, old_dir);
+                       error = PTR_ERR(old_dentry);
+                       if (IS_ERR(old_dentry))
+                               goto exit3;
+                       error = -ENOENT;
+                       if (!old_dentry->d_inode)
+                               goto exit4;
+                       if (S_ISDIR(old_dentry->d_inode->i_mode))
+                               break;
+                       error = -ENOTDIR;
+                       if (oldnd.last.name[oldnd.last.len])
+                               goto exit4;
+                       if (newnd.last.name[newnd.last.len])
+                               goto exit4;
+                       break;
+               case LAST_DOT:
+                       old_dentry = dget(oldnd.dentry);
+                       is_dir = 1;
+                       break;
+               case LAST_DOTDOT:
+                       is_dir = 1;
+                       old_dentry = dget(oldnd.dentry->d_parent);
+                       break;
+       }
+       switch(newnd.last_type) {
+               default:
+                       new_dentry = lookup_hash(&newnd.last, new_dir);
+                       error = PTR_ERR(new_dentry);
+                       if (IS_ERR(new_dentry))
+                               goto exit4;
+                       break;  
+               case LAST_DOT:
+                       new_dentry = dget(newnd.dentry);
+                       break;
+       }
+       /*
+        * But wait, that's not all - if something sucked it might be
+        * removed.
+        */
+       if (d_unhashed(old_dentry) || d_unhashed(new_dentry))
+               goto Down_Not_across; /* see alt.suicide FAQ */
 
-       error = -ENOENT;
-       if (check_parent(old_dir, old_dentry) && check_parent(new_dir, new_dentry))
-               error = vfs_rename(old_dir->d_inode, old_dentry,
+       /* Finally */
+
+       error = vfs_rename(old_dir->d_inode, old_dentry,
                                   new_dir->d_inode, new_dentry);
 
-       double_unlock(new_dir, old_dir);
        dput(new_dentry);
-exit_old:
+exit4:
        dput(old_dentry);
+exit3:
+       double_unlock(new_dir, old_dir);
+exit2:
+       dput(newnd.dentry);
+       mntput(newnd.mnt);
+exit1:
+       dput(oldnd.dentry);
+       mntput(oldnd.mnt);
 exit:
        return error;
+
+Down_Not_across:
+       dput(new_dentry);
+       dput(old_dentry);
+       double_unlock(new_dir, old_dir);
+       dput(newnd.dentry);
+       mntput(newnd.mnt);
+       dput(oldnd.dentry);
+       mntput(oldnd.mnt);
+       goto retry;
 }
 
 asmlinkage long sys_rename(const char * oldname, const char * newname)
diff -urN linux-2.3.99-pre6-5/fs/nfsd/export.c linux-bird.vfs-1/fs/nfsd/export.c
--- linux-2.3.99-pre6-5/fs/nfsd/export.c        Tue Apr 11 18:09:48 2000
+++ linux-bird.vfs-1/fs/nfsd/export.c   Mon Apr 24 18:55:32 2000
@@ -103,23 +103,6 @@
        return exp;
 }
 
-
-/*
- * Look up the device of the parent fs.
- */
-static inline int
-nfsd_parentdev(kdev_t *devp)
-{
-       struct super_block      *sb;
-
-       if (!(sb = get_super(*devp)) || !sb->s_root->d_covers)
-               return 0;
-       if (*devp == sb->s_root->d_covers->d_inode->i_dev)
-               return 0;
-       *devp = sb->s_root->d_covers->d_inode->i_dev;
-       return 1;
-}
-
 /*
  * Find the export entry for a given dentry.  <[EMAIL PROTECTED]>
  */
@@ -127,36 +110,13 @@
 exp_parent(svc_client *clp, kdev_t dev, struct dentry *dentry)
 {
        svc_export      *exp;
-       kdev_t          xdev = dev;
-       struct dentry   *xdentry = dentry;
-       struct dentry   *ndentry = NULL;
 
-       if (clp == NULL || dentry == NULL)
+       if (clp == NULL)
                return NULL;
 
-       do {
-               xdev = dev;
-               do {
-                       exp = clp->cl_export[EXPORT_HASH(xdev)];
-                       if (exp)
-                               do {
-                                       ndentry = exp->ex_dentry;
-                                       if (ndentry == xdentry) {
-#ifdef NFSD_PARANOIA
-if (dev == xdev)
-       dprintk("nfsd: exp_parent submount over mount.\n");
-else
-       dprintk("nfsd: exp_parent found.\n");
-#endif
-                                               goto out;
-                                       }
-                               } while (NULL != (exp = exp->ex_next));
-               } while (nfsd_parentdev(&xdev));
-               if (IS_ROOT(xdentry))
+       for (exp = clp->cl_export[EXPORT_HASH(dev)]; exp; exp = exp->ex_next)
+               if (is_subdir(dentry, exp->ex_dentry))
                        break;
-       } while ((xdentry = xdentry->d_parent));
-       exp = NULL;
-out:
        return exp;
 }
 
@@ -169,30 +129,15 @@
 exp_child(svc_client *clp, kdev_t dev, struct dentry *dentry)
 {
        svc_export      *exp;
-       struct dentry   *xdentry = dentry;
-       struct dentry   *ndentry = NULL;
 
-       if (clp == NULL || dentry == NULL)
+       if (clp == NULL)
                return NULL;
 
-       exp = clp->cl_export[EXPORT_HASH(dev)];
-       if (exp)
-               do {
-                       ndentry = exp->ex_dentry;
-                       if (ndentry)
-                               while ((ndentry = ndentry->d_parent)) {
-                                       if (ndentry == xdentry) {
-#ifdef NFSD_PARANOIA
-dprintk("nfsd: exp_child mount under submount.\n");
-#endif
-                                               goto out;
-                                       }
-                                       if (IS_ROOT(ndentry))
-                                               break;
-                               }
-               } while (NULL != (exp = exp->ex_next));
-       exp = NULL;
-out:
+       for (exp = clp->cl_export[EXPORT_HASH(dev)]; exp; exp = exp->ex_next) {
+               struct dentry   *ndentry = exp->ex_dentry;
+               if (ndentry && is_subdir(ndentry->d_parent, dentry))
+                       break;
+       }
        return exp;
 }
 
@@ -279,10 +224,8 @@
        }
        /* Is this is a sub-export, must be a proper subset of FS */
        if ((parent = exp_parent(clp, dev, dentry)) != NULL) {
-               if (dev == parent->ex_dev) {
-                       dprintk("exp_export: sub-export not valid (Rule 2).\n");
-                       goto finish;
-               }
+               dprintk("exp_export: sub-export not valid (Rule 2).\n");
+               goto finish;
        }
 
        err = -ENOMEM;
diff -urN linux-2.3.99-pre6-5/fs/qnx4/namei.c linux-bird.vfs-1/fs/qnx4/namei.c
--- linux-2.3.99-pre6-5/fs/qnx4/namei.c Wed Mar  8 12:04:09 2000
+++ linux-bird.vfs-1/fs/qnx4/namei.c    Mon Apr 24 18:55:47 2000
@@ -171,10 +171,6 @@
                goto end_rmdir;
        }
 #endif
-       if (!d_unhashed(dentry)) {
-               retval = -EBUSY;
-               goto end_rmdir;
-       }
        if (inode->i_nlink != 2) {
                QNX4DEBUG(("empty directory has nlink!=2 (%d)\n", inode->i_nlink));
        }
diff -urN linux-2.3.99-pre6-5/fs/readdir.c linux-bird.vfs-1/fs/readdir.c
--- linux-2.3.99-pre6-5/fs/readdir.c    Mon Mar  6 18:49:21 2000
+++ linux-bird.vfs-1/fs/readdir.c       Mon Apr 24 18:55:47 2000
@@ -22,7 +22,11 @@
        if (!file->f_op || !file->f_op->readdir)
                goto out;
        down(&inode->i_sem);
-       res = file->f_op->readdir(file, buf, filler);
+       down(&inode->i_zombie);
+       res = -ENOENT;
+       if (!IS_DEADDIR(inode))
+               res = file->f_op->readdir(file, buf, filler);
+       up(&inode->i_zombie);
        up(&inode->i_sem);
 out:
        return res;
diff -urN linux-2.3.99-pre6-5/fs/super.c linux-bird.vfs-1/fs/super.c
--- linux-2.3.99-pre6-5/fs/super.c      Fri Apr 21 21:58:59 2000
+++ linux-bird.vfs-1/fs/super.c Mon Apr 24 18:55:32 2000
@@ -76,6 +76,13 @@
 static struct file_system_type *file_systems = NULL;
 static spinlock_t file_systems_lock = SPIN_LOCK_UNLOCKED;
 
+/* WARNING: This can be used only if we _already_ own a reference */
+static void get_filesystem(struct file_system_type *fs)
+{
+       if (fs->owner)
+               __MOD_INC_USE_COUNT(fs->owner);
+}
+
 static void put_filesystem(struct file_system_type *fs)
 {
        if (fs->owner)
@@ -269,79 +276,55 @@
        return fs;
 }
 
-struct vfsmount *vfsmntlist = NULL;
-static struct vfsmount *vfsmnttail = NULL, *mru_vfsmnt = NULL;
+static LIST_HEAD(vfsmntlist);
 
 static struct vfsmount *add_vfsmnt(struct super_block *sb,
                        const char *dev_name, const char *dir_name)
 {
-       struct vfsmount *lptr;
+       struct vfsmount *mnt;
        char *name;
 
-       lptr = (struct vfsmount *)kmalloc(sizeof(struct vfsmount), GFP_KERNEL);
-       if (!lptr)
+       mnt = (struct vfsmount *)kmalloc(sizeof(struct vfsmount), GFP_KERNEL);
+       if (!mnt)
                goto out;
-       memset(lptr, 0, sizeof(struct vfsmount));
+       memset(mnt, 0, sizeof(struct vfsmount));
 
-       lptr->mnt_sb = sb;
-       lptr->mnt_dev = sb->s_dev;
+       mnt->mnt_sb = sb;
+       mnt->mnt_dev = sb->s_dev;
 
        /* N.B. Is it really OK to have a vfsmount without names? */
        if (dev_name) {
                name = (char *) kmalloc(strlen(dev_name)+1, GFP_KERNEL);
                if (name) {
                        strcpy(name, dev_name);
-                       lptr->mnt_devname = name;
+                       mnt->mnt_devname = name;
                }
        }
        if (dir_name) {
                name = (char *) kmalloc(strlen(dir_name)+1, GFP_KERNEL);
                if (name) {
                        strcpy(name, dir_name);
-                       lptr->mnt_dirname = name;
+                       mnt->mnt_dirname = name;
                }
        }
 
-       if (vfsmntlist == (struct vfsmount *)NULL) {
-               vfsmntlist = vfsmnttail = lptr;
-       } else {
-               vfsmnttail->mnt_next = lptr;
-               vfsmnttail = lptr;
-       }
+       list_add(&mnt->mnt_list, vfsmntlist.prev);
 out:
-       return lptr;
+       return mnt;
 }
 
 void remove_vfsmnt(kdev_t dev)
 {
-       struct vfsmount *lptr, *tofree;
-
-       if (vfsmntlist == NULL)
-               return;
-       lptr = vfsmntlist;
-       if (lptr->mnt_dev == dev) {
-               tofree = lptr;
-               vfsmntlist = lptr->mnt_next;
-               if (vfsmnttail->mnt_dev == dev)
-                       vfsmnttail = vfsmntlist;
-       } else {
-               while (lptr->mnt_next != NULL) {
-                       if (lptr->mnt_next->mnt_dev == dev)
-                               break;
-                       lptr = lptr->mnt_next;
-               }
-               tofree = lptr->mnt_next;
-               if (tofree == NULL)
-                       return;
-               lptr->mnt_next = lptr->mnt_next->mnt_next;
-               if (vfsmnttail->mnt_dev == dev)
-                       vfsmnttail = lptr;
-       }
-       if (tofree == mru_vfsmnt)
-               mru_vfsmnt = NULL;
-       kfree(tofree->mnt_devname);
-       kfree(tofree->mnt_dirname);
-       kfree_s(tofree, sizeof(struct vfsmount));
+       struct list_head *p;
+       for (p = vfsmntlist.next; p!=&vfsmntlist; p = p->next) {
+               struct vfsmount *mnt = list_entry(p, struct vfsmount, mnt_list);
+               if (mnt->mnt_dev != dev)
+                       continue;
+               list_del(&mnt->mnt_list);
+               kfree(mnt->mnt_devname);
+               kfree(mnt->mnt_dirname);
+               kfree_s(mnt, sizeof(struct vfsmount));
+       }
 }
 
 static struct proc_fs_info {
@@ -378,7 +361,7 @@
 
 int get_filesystem_info( char *buf )
 {
-       struct vfsmount *tmp;
+       struct list_head *p;
        struct proc_fs_info *fs_infop;
        struct proc_nfs_info *nfs_infop;
        struct nfs_server *nfss;
@@ -386,7 +369,9 @@
        char *path,*buffer = (char *) __get_free_page(GFP_KERNEL);
 
        if (!buffer) return 0;
-       for (tmp = vfsmntlist; tmp && len < PAGE_SIZE - 160; tmp = tmp->mnt_next) {
+       for (p = vfsmntlist.next; p!=&vfsmntlist && len < PAGE_SIZE - 160;
+           p = p->next) {
+               struct vfsmount *tmp = list_entry(p, struct vfsmount, mnt_list);
                if (!tmp->mnt_sb || !tmp->mnt_sb->s_root)
                        continue;
                path = d_path(tmp->mnt_sb->s_root, tmp, buffer, PAGE_SIZE);
@@ -495,14 +480,11 @@
                        continue;
                if (!sb->s_dirt)
                        continue;
-               /* N.B. Should lock the superblock while writing */
-               wait_on_super(sb);
-               if (!sb->s_dev || !sb->s_dirt)
-                       continue;
-               if (dev && (dev != sb->s_dev))
-                       continue;
-               if (sb->s_op && sb->s_op->write_super)
-                       sb->s_op->write_super(sb);
+               lock_super(sb);
+               if (sb->s_dev && sb->s_dirt && (!dev || dev == sb->s_dev))
+                       if (sb->s_op && sb->s_op->write_super)
+                               sb->s_op->write_super(sb);
+               unlock_super(sb);
        }
 }
 
@@ -659,6 +641,165 @@
                        kdevname(dev));
 }
 
+static struct super_block *get_sb_bdev(struct file_system_type *fs_type,
+       char *dev_name, int flags, void * data)
+{
+       struct dentry *dentry;
+       struct inode *inode;
+       struct block_device *bdev;
+       struct block_device_operations *bdops;
+       struct super_block * sb;
+       kdev_t dev;
+       int error;
+       /* What device it is? */
+       if (!dev_name || !*dev_name)
+               return ERR_PTR(-EINVAL);
+       dentry = lookup_dentry(dev_name, LOOKUP_FOLLOW|LOOKUP_POSITIVE);
+       if (IS_ERR(dentry))
+               return (struct super_block *)dentry;
+       inode = dentry->d_inode;
+       error = -ENOTBLK;
+       if (!S_ISBLK(inode->i_mode))
+               goto out;
+       error = -EACCES;
+       if (IS_NODEV(inode))
+               goto out;
+       bdev = inode->i_bdev;
+       bdops = devfs_get_ops ( devfs_get_handle_from_inode (inode) );
+       if (bdops) bdev->bd_op = bdops;
+       /* Done with lookups, semaphore down */
+       down(&mount_sem);
+       dev = to_kdev_t(bdev->bd_dev);
+       check_disk_change(dev);
+       error = -EACCES;
+       if (!(flags & MS_RDONLY) && is_read_only(dev))
+               goto out;
+       sb = get_super(dev);
+       if (sb) {
+               error = -EBUSY;
+               goto out;
+               /* MOUNT_REWRITE: the following should be used
+               if (fs_type == sb->s_type) {
+                       dput(dentry);
+                       return sb;
+               }
+               */
+       } else {
+               mode_t mode = FMODE_READ; /* we always need it ;-) */
+               if (!(flags & MS_RDONLY))
+                       mode |= FMODE_WRITE;
+               error = blkdev_get(bdev, mode, 0, BDEV_FS);
+               if (error)
+                       goto out;
+               error = -EINVAL;
+               sb = read_super(dev, bdev, fs_type, flags, data, 0);
+               if (sb) {
+                       get_filesystem(fs_type);
+                       dput(dentry);
+                       return sb;
+               }
+               blkdev_put(bdev, BDEV_FS);
+       }
+out:
+       dput(dentry);
+       up(&mount_sem);
+       return ERR_PTR(error);
+}
+
+static struct super_block *get_sb_nodev(struct file_system_type *fs_type,
+       int flags, void * data)
+{
+       kdev_t dev;
+       int error = -EMFILE;
+       down(&mount_sem);
+       dev = get_unnamed_dev();
+       if (dev) {
+               struct super_block * sb;
+               error = -EINVAL;
+               sb = read_super(dev, NULL, fs_type, flags, data, 0);
+               if (sb) {
+                       get_filesystem(fs_type);
+                       return sb;
+               }
+               put_unnamed_dev(dev);
+       }
+       up(&mount_sem);
+       return ERR_PTR(error);
+}
+
+static struct block_device *kill_super(struct super_block *sb, int umount_root)
+{
+       struct block_device *bdev;
+       kdev_t dev;
+       lock_super(sb);
+       if (sb->s_op) {
+               if (sb->s_op->write_super && sb->s_dirt)
+                       sb->s_op->write_super(sb);
+               if (sb->s_op->put_super)
+                       sb->s_op->put_super(sb);
+       }
+
+       /* Forget any remaining inodes */
+       if (invalidate_inodes(sb)) {
+               printk("VFS: Busy inodes after unmount. "
+                       "Self-destruct in 5 seconds.  Have a nice day...\n");
+       }
+
+       dev = sb->s_dev;
+       sb->s_dev = 0;          /* Free the superblock */
+       bdev = sb->s_bdev;
+       sb->s_bdev = NULL;
+       put_filesystem(sb->s_type);
+       sb->s_type = NULL;
+       unlock_super(sb);
+       if (umount_root) {
+               /* special: the old device driver is going to be
+                  a ramdisk and the point of this call is to free its
+                  protected memory (even if dirty). */
+               destroy_buffers(dev);
+       }
+       if (bdev) {
+               blkdev_put(bdev, BDEV_FS);
+               bdput(bdev);
+       } else
+               put_unnamed_dev(dev);
+       return bdev;
+}
+
+/*
+ * Alters the mount flags of a mounted file system. Only the mount point
+ * is used as a reference - file system type and the device are ignored.
+ */
+
+static int do_remount_sb(struct super_block *sb, int flags, char *data)
+{
+       int retval;
+       
+       if (!(flags & MS_RDONLY) && sb->s_dev && is_read_only(sb->s_dev))
+               return -EACCES;
+               /*flags |= MS_RDONLY;*/
+       /* If we are remounting RDONLY, make sure there are no rw files open */
+       if ((flags & MS_RDONLY) && !(sb->s_flags & MS_RDONLY))
+               if (!fs_may_remount_ro(sb))
+                       return -EBUSY;
+       if (sb->s_op && sb->s_op->remount_fs) {
+               lock_super(sb);
+               retval = sb->s_op->remount_fs(sb, &flags, data);
+               unlock_super(sb);
+               if (retval)
+                       return retval;
+       }
+       sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK);
+
+       /*
+        * We can't invalidate inodes as we can loose data when remounting
+        * (someone might manage to alter data while we are waiting in lock_super()
+        * or in foo_remount_fs()))
+        */
+
+       return 0;
+}
+
 static int d_umount(struct super_block * sb)
 {
        struct dentry * root = sb->s_root;
@@ -747,32 +888,8 @@
        retval = d_umount(sb);
        if (retval)
                goto out;
-
-       if (sb->s_op) {
-               if (sb->s_op->write_super && sb->s_dirt)
-                       sb->s_op->write_super(sb);
-       }
-
-       lock_super(sb);
-       if (sb->s_op) {
-               if (sb->s_op->put_super)
-                       sb->s_op->put_super(sb);
-       }
-
-       /* Forget any remaining inodes */
-       if (invalidate_inodes(sb)) {
-               printk("VFS: Busy inodes after unmount. "
-                       "Self-destruct in 5 seconds.  Have a nice day...\n");
-       }
-
-       sb->s_dev = 0;          /* Free the superblock */
-       bdev = sb->s_bdev;
-       sb->s_bdev = NULL;
-       put_filesystem(sb->s_type);
-       sb->s_type = NULL;
-       unlock_super(sb);
-
        remove_vfsmnt(dev);
+       bdev = kill_super(sb, unmount_root);
 
        return bdev;
 
@@ -796,15 +913,8 @@
        bdev = do_umount(dev, 0, flags);
        if (IS_ERR(bdev))
                retval = PTR_ERR(bdev);
-       else {
+       else
                retval = 0;
-               if (bdev) {
-                       blkdev_put(bdev, BDEV_FS);
-                       bdput(bdev);
-               } else {
-                       put_unnamed_dev(dev);
-               }
-       }
        up(&mount_sem);
 out:
        return retval;
@@ -877,170 +987,19 @@
 }
 
 /*
- * do_mount() does the actual mounting after sys_mount has done the ugly
- * parameter parsing. When enough time has gone by, and everything uses the
- * new mount() parameters, sys_mount() can then be cleaned up.
- *
- * We cannot mount a filesystem if it has active, used, or dirty inodes.
- * We also have to flush all inode-data for this device, as the new mount
- * might need new info.
- *
- * [21-Mar-97] T.Schoebel-Theuer: Now this can be overridden when
- * supplying a leading "!" before the dir_name, allowing "stacks" of
- * mounted filesystems. The stacking will only influence any pathname lookups
- * _after_ the mount, but open file descriptors or working directories that
- * are now covered remain valid. For example, when you overmount /home, any
- * process with old cwd /home/joe will continue to use the old versions,
- * as long as relative paths are used, but absolute paths like /home/joe/xxx
- * will go to the new "top of stack" version. In general, crossing a
- * mount point will always go to the top of stack element.
- * Anyone using this new feature must know what he/she is doing.
- */
-
-static int do_mount(struct block_device *bdev, const char *dev_name,
-            const char *dir_name, const char * type, int flags, void * data)
-{
-       kdev_t dev;
-       struct dentry * dir_d;
-       struct super_block * sb;
-       struct vfsmount *vfsmnt;
-       struct file_system_type *fs_type;
-       int error;
-
-       if (bdev) {
-               mode_t mode = FMODE_READ; /* we always need it ;-) */
-               if (!(flags & MS_RDONLY))
-                       mode |= FMODE_WRITE;
-               dev = to_kdev_t(bdev->bd_dev);
-               error = blkdev_get(bdev, mode, 0, BDEV_FS);
-               if (error)
-                       return error;
-       } else {
-               dev = get_unnamed_dev();
-               if (!dev)
-                       return -EMFILE; /* huh? */
-       }
-
-       error = -EACCES;
-       if (!(flags & MS_RDONLY) && dev && is_read_only(dev))
-               goto out;
-
-       /*
-        * Do the lookup first to force automounting.
-        */
-       dir_d = lookup_dentry(dir_name, LOOKUP_FOLLOW|LOOKUP_POSITIVE);
-       error = PTR_ERR(dir_d);
-       if (IS_ERR(dir_d))
-               goto out;
-
-       down(&mount_sem);
-       error = -ENOTDIR;
-       if (!S_ISDIR(dir_d->d_inode->i_mode))
-               goto dput_and_out;
-
-       error = -EBUSY;
-       if (dir_d->d_covers != dir_d)
-               goto dput_and_out;
-
-       error = -EINVAL;
-       if (!dev)
-               goto dput_and_out;
-       check_disk_change(dev);
-       sb = get_super(dev);
-       if (sb) {
-               /* Already mounted */
-               error = -EBUSY;
-               goto dput_and_out;
-       }
-
-       fs_type = get_fs_type(type);
-       if (!fs_type) {
-               printk("VFS: on device %s: get_fs_type(%s) failed\n",
-                      kdevname(dev), type);
-               goto dput_and_out;
-       }
-
-       sb = read_super(dev, bdev, fs_type, flags, data, 0);
-       if (!sb)
-               goto fsput_and_out;
-
-       /*
-        * We may have slept while reading the super block, 
-        * so we check afterwards whether it's safe to mount.
-        */
-       error = -EBUSY;
-       if (!fs_may_mount(dev))
-               goto bdput_and_out;
-
-       error = -ENOMEM;
-       vfsmnt = add_vfsmnt(sb, dev_name, dir_name);
-       if (vfsmnt) {
-               d_mount(dget(dir_d), sb->s_root);
-               dput(dir_d);
-               up(&mount_sem);
-               return 0;
-       }
-
-bdput_and_out:
-       /* FIXME: ->put_super() is needed here */
-       sb->s_bdev = NULL;
-       sb->s_dev = 0;
-       sb->s_type = NULL;
-       if (bdev)
-               bdput(bdev);
-fsput_and_out:
-       put_filesystem(fs_type);
-dput_and_out:
-       dput(dir_d);
-       up(&mount_sem);
-out:
-       if (bdev)
-               blkdev_put(bdev, BDEV_FS);
-       else
-               put_unnamed_dev(dev);
-       return error;
-}
-
-
-/*
- * Alters the mount flags of a mounted file system. Only the mount point
- * is used as a reference - file system type and the device are ignored.
+ * change filesystem flags. dir should be a physical root of filesystem.
+ * If you've mounted a non-root directory somewhere and want to do remount
+ * on it - tough luck.
  */
 
-static int do_remount_sb(struct super_block *sb, int flags, char *data)
-{
-       int retval;
-       
-       if (!(flags & MS_RDONLY) && sb->s_dev && is_read_only(sb->s_dev))
-               return -EACCES;
-               /*flags |= MS_RDONLY;*/
-       /* If we are remounting RDONLY, make sure there are no rw files open */
-       if ((flags & MS_RDONLY) && !(sb->s_flags & MS_RDONLY))
-               if (!fs_may_remount_ro(sb))
-                       return -EBUSY;
-       if (sb->s_op && sb->s_op->remount_fs) {
-               lock_super(sb);
-               retval = sb->s_op->remount_fs(sb, &flags, data);
-               unlock_super(sb);
-               if (retval)
-                       return retval;
-       }
-       sb->s_flags = (sb->s_flags & ~MS_RMT_MASK) | (flags & MS_RMT_MASK);
-
-       /*
-        * We can't invalidate inodes as we can loose data when remounting
-        * (someone might manage to alter data while we are waiting in lock_super()
-        * or in foo_remount_fs()))
-        */
-
-       return 0;
-}
-
 static int do_remount(const char *dir,int flags,char *data)
 {
        struct dentry *dentry;
        int retval;
 
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
        dentry = lookup_dentry(dir, LOOKUP_FOLLOW|LOOKUP_POSITIVE);
        retval = PTR_ERR(dentry);
        if (!IS_ERR(dentry)) {
@@ -1111,9 +1070,9 @@
                  unsigned long new_flags, void *data_page)
 {
        struct file_system_type * fstype;
-       struct dentry * dentry = NULL;
-       struct inode * inode = NULL;
-       struct block_device *bdev = NULL;
+       struct dentry * dir_d;
+       struct vfsmount *mnt;
+       struct super_block *sb;
        int retval;
        unsigned long flags = 0;
  
@@ -1126,59 +1085,84 @@
        if (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
                return -EINVAL;
 
-       if (!capable(CAP_SYS_ADMIN))
-               return -EPERM;
+       /* OK, looks good, now let's see what do they want */
 
-       if ((new_flags &
-            (MS_MGC_MSK | MS_REMOUNT)) == (MS_MGC_VAL | MS_REMOUNT)) {
-               retval = do_remount(dir_name,
-                                   new_flags & ~MS_MGC_MSK & ~MS_REMOUNT,
+       /* just change the flags? - capabilities are checked in do_remount() */
+       if ((new_flags & (MS_MGC_MSK|MS_REMOUNT)) == (MS_MGC_VAL|MS_REMOUNT))
+               return do_remount(dir_name, new_flags&~(MS_MGC_MSK|MS_REMOUNT),
                                    (char *) data_page);
-               goto out;
-       }
 
+       if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL)
+               flags = new_flags & ~MS_MGC_MSK;
+
+       /* loopback mount? This is special - requires fewer capabilities */
+       /* MOUNT_REWRITE: ... and is yet to be merged */
+
+       /* for the rest we _really_ need capabilities... */
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       /* ... filesystem driver... */
        fstype = get_fs_type(type_page);
-       retval = -ENODEV;
        if (!fstype)            
-               goto out;
+               return -ENODEV;
 
-       if (fstype->fs_flags & FS_REQUIRES_DEV) {
-               struct block_device_operations *bdops;
+       /* ... and mountpoint. Do the lookup first to force automounting. */
+       dir_d = lookup_dentry(dir_name, LOOKUP_FOLLOW|LOOKUP_POSITIVE);
+       retval = PTR_ERR(dir_d);
+       if (IS_ERR(dir_d))
+               goto fs_out;
 
-               retval = -EINVAL;
-               if (!dev_name || !*dev_name)
-                       goto fs_out;
-               dentry = lookup_dentry(dev_name, LOOKUP_FOLLOW|LOOKUP_POSITIVE);
-               retval = PTR_ERR(dentry);
-               if (IS_ERR(dentry))
-                       goto fs_out;
-
-               inode = dentry->d_inode;
-               retval = -ENOTBLK;
-               if (!S_ISBLK(inode->i_mode))
-                       goto dput_and_out;
-
-               retval = -EACCES;
-               if (IS_NODEV(inode))
-                       goto dput_and_out;
-
-               bdev = inode->i_bdev;
-               bdops = devfs_get_ops ( devfs_get_handle_from_inode (inode) );
-               if (bdops) bdev->bd_op = bdops;
-       }
+       /* get superblock, locks mount_sem on success */
+       if (fstype->fs_flags & FS_REQUIRES_DEV)
+               sb = get_sb_bdev(fstype, dev_name,flags, data_page);
+       else
+               sb = get_sb_nodev(fstype, flags, data_page);
 
-       if ((new_flags & MS_MGC_MSK) == MS_MGC_VAL)
-               flags = new_flags & ~MS_MGC_MSK;
+       retval = PTR_ERR(sb);
+       if (IS_ERR(sb))
+               goto dput_out;
 
-       retval = do_mount(bdev, dev_name, dir_name, fstype->name, flags,
-                               data_page);
+       retval = -ENOENT;
+       if (d_unhashed(dir_d))
+               goto fail;
 
-dput_and_out:
-       dput(dentry);
+       retval = -ENOTDIR;
+       if (!S_ISDIR(dir_d->d_inode->i_mode))
+               goto fail;
+
+       retval = -EBUSY;
+       if (dir_d->d_covers != dir_d)
+               goto fail;
+
+       /*
+        * We may have slept while reading the super block, 
+        * so we check afterwards whether it's safe to mount.
+        */
+       retval = -EBUSY;
+       if (!fs_may_mount(sb->s_dev))
+               goto fail;
+
+       retval = -ENOMEM;
+       mnt = add_vfsmnt(sb, dev_name, dir_name);
+       if (!mnt)
+               goto fail;
+       d_mount(dget(dir_d), sb->s_root);
+
+       retval = 0;
+unlock_out:
+       up(&mount_sem);
+dput_out:
+       dput(dir_d);
 fs_out:
        put_filesystem(fstype);
-out:
        return retval;
+
+fail:
+       dput(sb->s_root);
+       sb->s_root = NULL;
+       kill_super(sb, 0);
+       goto unlock_out;
 }
 
 asmlinkage long sys_mount(char * dev_name, char * dir_name, char * type,
@@ -1562,14 +1546,6 @@
                bdev = do_umount(old_root_dev,1, 0);
                if (!IS_ERR(bdev)) {
                        printk("okay\n");
-                       /* special: the old device driver is going to be
-                          a ramdisk and the point of this call is to free its
-                          protected memory (even if dirty). */
-                       destroy_buffers(old_root_dev);
-                       if (bdev) {
-                               blkdev_put(bdev, BDEV_FS);
-                               bdput(bdev);
-                       }
                        return 0;
                }
                printk(KERN_ERR "error %ld\n",PTR_ERR(bdev));
diff -urN linux-2.3.99-pre6-5/fs/sysv/inode.c linux-bird.vfs-1/fs/sysv/inode.c
--- linux-2.3.99-pre6-5/fs/sysv/inode.c Fri Mar 24 03:22:18 2000
+++ linux-bird.vfs-1/fs/sysv/inode.c    Mon Apr 24 18:55:32 2000
@@ -516,7 +516,6 @@
 /* This is only called on sync() and umount(), when s_dirt=1. */
 static void sysv_write_super(struct super_block *sb)
 {
-       lock_super(sb);
        if (buffer_dirty(sb->sv_bh1) || buffer_dirty(sb->sv_bh2)) {
                /* If we are going to write out the super block,
                   then attach current time stamp.
@@ -534,7 +533,6 @@
                mark_buffer_dirty(sb->sv_bh2, 1);
        }
        sb->s_dirt = 0;
-       unlock_super(sb);
 }
 
 static void sysv_put_super(struct super_block *sb)
diff -urN linux-2.3.99-pre6-5/fs/sysv/namei.c linux-bird.vfs-1/fs/sysv/namei.c
--- linux-2.3.99-pre6-5/fs/sysv/namei.c Wed Mar  8 12:04:09 2000
+++ linux-bird.vfs-1/fs/sysv/namei.c    Mon Apr 24 18:55:47 2000
@@ -389,10 +389,6 @@
                retval = -ENOTEMPTY;
                goto end_rmdir;
        }
-       if (!d_unhashed(dentry)) {
-               retval = -EBUSY;
-               goto end_rmdir;
-       }
        if (inode->i_nlink != 2)
                printk("empty directory has nlink!=2 (%d)\n", inode->i_nlink);
        de->inode = 0;
@@ -552,9 +548,6 @@
        }
        if (S_ISDIR(old_inode->i_mode)) {
                if (new_inode) {
-                       retval = -EBUSY;
-                       if (!d_unhashed(new_dentry))
-                               goto end_rename;
                        retval = -ENOTEMPTY;
                        if (!empty_dir(new_inode))
                                goto end_rename;
diff -urN linux-2.3.99-pre6-5/fs/udf/namei.c linux-bird.vfs-1/fs/udf/namei.c
--- linux-2.3.99-pre6-5/fs/udf/namei.c  Fri Apr 21 21:58:59 2000
+++ linux-bird.vfs-1/fs/udf/namei.c     Mon Apr 24 18:55:47 2000
@@ -344,9 +344,6 @@
        Uint32 extoffset, elen, offset;
        struct buffer_head *bh = NULL;
 
-       *err = -EINVAL;
-       if (!dir || !dir->i_nlink)
-               return NULL;
        sb = dir->i_sb;
 
        if (dentry->d_name.len)
@@ -365,6 +362,7 @@
        }
        else if (dir->i_size != 0)
        {
+               /* WTF??? */
                *err = -ENOENT;
                return NULL;
        }
@@ -1165,9 +1163,6 @@
 
                if (new_inode)
                {
-                       retval = -EBUSY;
-                       if (!d_unhashed(new_dentry))
-                               goto end_rename;
                        retval = -ENOTEMPTY;
                        if (!empty_dir(new_inode))
                                goto end_rename;
diff -urN linux-2.3.99-pre6-5/fs/ufs/namei.c linux-bird.vfs-1/fs/ufs/namei.c
--- linux-2.3.99-pre6-5/fs/ufs/namei.c  Sat Feb 26 23:33:09 2000
+++ linux-bird.vfs-1/fs/ufs/namei.c     Mon Apr 24 18:55:47 2000
@@ -240,8 +240,6 @@
        
        *err = -EINVAL;
        *res_dir = NULL;
-       if (!dir || !dir->i_nlink)
-               return NULL;
                
        sb = dir->i_sb;
        flags = sb->u.ufs_sb.s_flags;
@@ -250,14 +248,6 @@
 
        if (!namelen)
                return NULL;
-       /*
-        * Is this a busy deleted directory?  Can't create new files if so
-        */
-       if (dir->i_size == 0)
-       {
-               *err = -ENOENT;
-               return NULL;
-       }
        bh = ufs_bread (dir, 0, 0, err);
        if (!bh)
                return NULL;
diff -urN linux-2.3.99-pre6-5/fs/vfat/namei.c linux-bird.vfs-1/fs/vfat/namei.c
--- linux-2.3.99-pre6-5/fs/vfat/namei.c Mon Mar 13 15:35:39 2000
+++ linux-bird.vfs-1/fs/vfat/namei.c    Mon Apr 24 18:55:47 2000
@@ -1080,9 +1080,6 @@
        struct buffer_head *bh = NULL;
        struct msdos_dir_entry *de;
 
-       if (!d_unhashed(dentry))
-               return -EBUSY;
-
        res = fat_dir_empty(dentry->d_inode);
        if (res)
                return res;
@@ -1202,9 +1199,6 @@
                }
 
                if (is_dir) {
-                       res =-EBUSY;
-                       if (!d_unhashed(new_dentry))
-                               goto rename_done;
                        res = fat_dir_empty(new_inode);
                        if (res)
                                goto rename_done;
diff -urN linux-2.3.99-pre6-5/include/linux/fs.h linux-bird.vfs-1/include/linux/fs.h
--- linux-2.3.99-pre6-5/include/linux/fs.h      Fri Apr 21 21:59:02 2000
+++ linux-bird.vfs-1/include/linux/fs.h Mon Apr 24 18:58:43 2000
@@ -98,6 +98,7 @@
 #define MS_ODD_RENAME  32768   /* Temporary stuff; will go away as soon
                                  * as nfs_rename() will be cleaned up
                                  */
+#define S_DEAD         (1<<16) /* removed, but still open directory */
 
 /*
  * Flags that can be altered by MS_REMOUNT
@@ -140,6 +141,7 @@
 #define IS_NOATIME(inode)      __IS_FLG(inode, MS_NOATIME)
 #define IS_NODIRATIME(inode)   __IS_FLG(inode, MS_NODIRATIME)
 
+#define IS_DEADDIR(inode)      ((inode)->i_flags & S_DEAD)
 
 /* the read-only stuff doesn't really belong here, but any other place is
    probably as bad and I don't want to create yet another include file. */
@@ -552,6 +554,7 @@
        struct vfsmount *mnt;
        struct qstr last;
        unsigned int flags;
+       int last_type;
 };
 
 #define FASYNC_MAGIC 0x4601
@@ -999,11 +1002,14 @@
  */
 #define LOOKUP_FOLLOW          (1)
 #define LOOKUP_DIRECTORY       (2)
-#define LOOKUP_SLASHOK         (4)
-#define LOOKUP_CONTINUE                (8)
-#define LOOKUP_POSITIVE                (16)
-#define LOOKUP_PARENT          (32)
-#define LOOKUP_NOALT           (64)
+#define LOOKUP_CONTINUE                (4)
+#define LOOKUP_POSITIVE                (8)
+#define LOOKUP_PARENT          (16)
+#define LOOKUP_NOALT           (32)
+/*
+ * Type of the last component on LOOKUP_PARENT
+ */
+enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT };
 
 /*
  * "descriptor" for what we're up to with a read for sendfile().
diff -urN linux-2.3.99-pre6-5/include/linux/mount.h 
linux-bird.vfs-1/include/linux/mount.h
--- linux-2.3.99-pre6-5/include/linux/mount.h   Tue Apr 11 18:09:50 2000
+++ linux-bird.vfs-1/include/linux/mount.h      Mon Apr 24 18:55:32 2000
@@ -17,7 +17,7 @@
   char *mnt_devname;                   /* Name of device e.g. /dev/dsk/hda1 */
   char *mnt_dirname;                   /* Name of directory mounted on */
   struct super_block *mnt_sb;          /* pointer to superblock */
-  struct vfsmount *mnt_next;           /* pointer to next in linkedlist */
+       struct list_head mnt_list;
 };
 
 /* MOUNT_REWRITE: fill these */

Reply via email to