patch-2.4.0-test3 linux/fs/namei.c

Next file: linux/fs/ncpfs/dir.c
Previous file: linux/fs/minix/inode.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.0-test2/linux/fs/namei.c linux/fs/namei.c
@@ -14,6 +14,7 @@
 /* [Feb-Apr 2000, AV] Rewrite to the new namespace architecture.
  */
 
+#include <linux/init.h>
 #include <linux/mm.h>
 #include <linux/proc_fs.h>
 #include <linux/smp_lock.h>
@@ -89,6 +90,12 @@
  *	if the pathname has trailing slashes - follow.
  *	otherwise - don't follow.
  * (applied in that order).
+ *
+ * [Jun 2000 AV] Inconsistent behaviour of open() in case if flags==O_CREAT
+ * restored for 2.4. This is the last surviving part of old 4.2BSD bug.
+ * During the 2.4 we need to fix the userland stuff depending on it -
+ * hopefully we will be able to get rid of that wart in 2.5. So far only
+ * XEmacs seems to be relying on it...
  */
 
 /* In order to reduce some races, while at the same time doing additional
@@ -191,33 +198,46 @@
  * < 0: (-i_writecount) vm_area_structs with VM_DENYWRITE set exist
  * > 0: (i_writecount) users are writing to the file.
  *
- * WARNING: as soon as we will move get_write_access(), do_mmap() or
- * prepare_binfmt() out of the big lock we will need a spinlock protecting
- * the checks in all 3. For the time being it is not needed.
+ * Normally we operate on that counter with atomic_{inc,dec} and it's safe
+ * except for the cases where we don't hold i_writecount yet. Then we need to
+ * use {get,deny}_write_access() - these functions check the sign and refuse
+ * to do the change if sign is wrong. Exclusion between them is provided by
+ * spinlock (arbitration_lock) and I'll rip the second arsehole to the first
+ * who will try to move it in struct inode - just leave it here.
  */
+static spinlock_t arbitration_lock = SPIN_LOCK_UNLOCKED;
 int get_write_access(struct inode * inode)
 {
-	if (atomic_read(&inode->i_writecount) < 0)
+	spin_lock(&arbitration_lock);
+	if (atomic_read(&inode->i_writecount) < 0) {
+		spin_unlock(&arbitration_lock);
 		return -ETXTBSY;
+	}
 	atomic_inc(&inode->i_writecount);
+	spin_unlock(&arbitration_lock);
 	return 0;
 }
-
-void put_write_access(struct inode * inode)
+int deny_write_access(struct file * file)
 {
-	atomic_dec(&inode->i_writecount);
+	spin_lock(&arbitration_lock);
+	if (atomic_read(&file->f_dentry->d_inode->i_writecount) > 0) {
+		spin_unlock(&arbitration_lock);
+		return -ETXTBSY;
+	}
+	atomic_dec(&file->f_dentry->d_inode->i_writecount);
+	spin_unlock(&arbitration_lock);
+	return 0;
 }
 
 void path_release(struct nameidata *nd)
 {
-	lock_kernel();
 	dput(nd->dentry);
 	mntput(nd->mnt);
-	unlock_kernel();
 }
 
 /*
  * Internal lookup() using the new generic dcache.
+ * SMP-safe
  */
 static struct dentry * cached_lookup(struct dentry * parent, struct qstr * name, int flags)
 {
@@ -238,6 +258,7 @@
  *
  * We get the directory semaphore, and after getting that we also
  * make sure that nobody added the entry to the dcache in the meantime..
+ * SMP-safe
  */
 static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, int flags)
 {
@@ -257,7 +278,9 @@
 		struct dentry * dentry = d_alloc(parent, name);
 		result = ERR_PTR(-ENOMEM);
 		if (dentry) {
+			lock_kernel();
 			result = dir->i_op->lookup(dir, dentry);
+			unlock_kernel();
 			if (result)
 				dput(dentry);
 			else
@@ -296,12 +319,17 @@
 
 static inline int __follow_up(struct vfsmount **mnt, struct dentry **base)
 {
-	struct vfsmount *parent=(*mnt)->mnt_parent;
+	struct vfsmount *parent;
 	struct dentry *dentry;
-	if (parent == *mnt)
+	spin_lock(&dcache_lock);
+	parent=(*mnt)->mnt_parent;
+	if (parent == *mnt) {
+		spin_unlock(&dcache_lock);
 		return 0;
+	}
 	mntget(parent);
 	dentry=dget((*mnt)->mnt_mountpoint);
+	spin_unlock(&dcache_lock);
 	dput(*base);
 	*base = dentry;
 	mntput(*mnt);
@@ -316,12 +344,15 @@
 
 static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
 {
-	struct list_head *p = (*dentry)->d_vfsmnt.next;
+	struct list_head *p;
+	spin_lock(&dcache_lock);
+	p = (*dentry)->d_vfsmnt.next;
 	while (p != &(*dentry)->d_vfsmnt) {
 		struct vfsmount *tmp;
 		tmp = list_entry(p, struct vfsmount, mnt_clash);
 		if (tmp->mnt_parent == *mnt) {
 			*mnt = mntget(tmp);
+			spin_unlock(&dcache_lock);
 			mntput(tmp->mnt_parent);
 			/* tmp holds the mountpoint, so... */
 			dput(*dentry);
@@ -330,6 +361,7 @@
 		}
 		p = p->next;
 	}
+	spin_unlock(&dcache_lock);
 	return 0;
 }
 
@@ -337,7 +369,41 @@
 {
 	return __follow_down(mnt,dentry);
 }
-
+ 
+static inline void follow_dotdot(struct nameidata *nd)
+{
+	while(1) {
+		struct vfsmount *parent;
+		struct dentry *dentry;
+		read_lock(&current->fs->lock);
+		if (nd->dentry == current->fs->root &&
+		    nd->mnt == current->fs->rootmnt)  {
+			read_unlock(&current->fs->lock);
+			break;
+		}
+		read_unlock(&current->fs->lock);
+		spin_lock(&dcache_lock);
+		if (nd->dentry != nd->mnt->mnt_root) {
+			dentry = dget(nd->dentry->d_parent);
+			spin_unlock(&dcache_lock);
+			dput(nd->dentry);
+			nd->dentry = dentry;
+			break;
+		}
+		parent=nd->mnt->mnt_parent;
+		if (parent == nd->mnt) {
+			spin_unlock(&dcache_lock);
+			break;
+		}
+		mntget(parent);
+		dentry=dget(nd->mnt->mnt_mountpoint);
+		spin_unlock(&dcache_lock);
+		dput(nd->dentry);
+		nd->dentry = dentry;
+		mntput(nd->mnt);
+		nd->mnt = parent;
+	}
+}
 /*
  * Name resolution.
  *
@@ -403,19 +469,7 @@
 			case 2:	
 				if (this.name[1] != '.')
 					break;
-				while (1) {
-					if (nd->dentry == current->fs->root &&
-					    nd->mnt == current->fs->rootmnt)
-						break;
-					if (nd->dentry != nd->mnt->mnt_root) {
-						dentry = dget(nd->dentry->d_parent);
-						dput(nd->dentry);
-						nd->dentry = dentry;
-						break;
-					}
-					if (!__follow_up(&nd->mnt, &nd->dentry))
-						break;
-				}
+				follow_dotdot(nd);
 				inode = nd->dentry->d_inode;
 				/* fallthrough */
 			case 1:
@@ -483,19 +537,7 @@
 			case 2:	
 				if (this.name[1] != '.')
 					break;
-				while (1) {
-					if (nd->dentry == current->fs->root &&
-					    nd->mnt == current->fs->rootmnt)
-						break;
-					if (nd->dentry != nd->mnt->mnt_root) {
-						dentry = dget(nd->dentry->d_parent);
-						dput(nd->dentry);
-						nd->dentry = dentry;
-						break;
-					}
-					if (!__follow_up(&nd->mnt, &nd->dentry))
-						break;
-				}
+				follow_dotdot(nd);
 				inode = nd->dentry->d_inode;
 				/* fallthrough */
 			case 1:
@@ -561,11 +603,10 @@
 	return err;
 }
 
+/* SMP-safe */
 /* returns 1 if everything is done */
 static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
 {
-	nd->mnt = mntget(current->fs->altrootmnt);
-	nd->dentry = dget(current->fs->altroot);
 	if (path_walk(name, nd))
 		return 0;
 
@@ -573,8 +614,10 @@
 		struct nameidata nd_root;
 		nd_root.last_type = LAST_ROOT;
 		nd_root.flags = nd->flags;
+		read_lock(&current->fs->lock);
 		nd_root.mnt = mntget(current->fs->rootmnt);
 		nd_root.dentry = dget(current->fs->root);
+		read_unlock(&current->fs->lock);
 		if (path_walk(name, &nd_root))
 			return 1;
 		if (nd_root.dentry->d_inode) {
@@ -596,49 +639,65 @@
 	struct vfsmount *mnt = NULL, *oldmnt;
 	struct dentry *dentry = NULL, *olddentry;
 	if (emul) {
+		read_lock(&current->fs->lock);
 		nd.mnt = mntget(current->fs->rootmnt);
 		nd.dentry = dget(current->fs->root);
+		read_unlock(&current->fs->lock);
 		nd.flags = LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_POSITIVE;
 		if (path_walk(emul,&nd) == 0) {
 			mnt = nd.mnt;
 			dentry = nd.dentry;
 		}
 	}
+	write_lock(&current->fs->lock);
 	oldmnt = current->fs->altrootmnt;
 	olddentry = current->fs->altroot;
 	current->fs->altrootmnt = mnt;
 	current->fs->altroot = dentry;
+	write_unlock(&current->fs->lock);
 	if (olddentry) {
 		dput(olddentry);
 		mntput(oldmnt);
 	}
 }
 
+/* SMP-safe */
 static inline int
 walk_init_root(const char *name, struct nameidata *nd)
 {
-	if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT))
+	read_lock(&current->fs->lock);
+	if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
+		nd->mnt = mntget(current->fs->altrootmnt);
+		nd->dentry = dget(current->fs->altroot);
+		read_unlock(&current->fs->lock);
 		if (__emul_lookup_dentry(name,nd))
 			return 0;
+		read_lock(&current->fs->lock);
+	}
 	nd->mnt = mntget(current->fs->rootmnt);
 	nd->dentry = dget(current->fs->root);
+	read_unlock(&current->fs->lock);
 	return 1;
 }
 
+/* SMP-safe */
 int path_init(const char *name,unsigned int flags,struct nameidata *nd)
 {
 	nd->last_type = LAST_ROOT; /* if there are only slashes... */
 	nd->flags = flags;
 	if (*name=='/')
 		return walk_init_root(name,nd);
+	read_lock(&current->fs->lock);
 	nd->mnt = mntget(current->fs->pwdmnt);
 	nd->dentry = dget(current->fs->pwd);
+	read_unlock(&current->fs->lock);
 	return 1;
 }
 
 /*
  * Restricted form of lookup. Doesn't follow links, single-component only,
  * needs parent already locked. Doesn't follow mounts.
+ * SMP-safe.
  */
 struct dentry * lookup_hash(struct qstr *name, struct dentry * base)
 {
@@ -669,7 +728,9 @@
 		dentry = ERR_PTR(-ENOMEM);
 		if (!new)
 			goto out;
+		lock_kernel();
 		dentry = inode->i_op->lookup(inode, new);
+		unlock_kernel();
 		if (!dentry)
 			dentry = new;
 		else
@@ -679,6 +740,7 @@
 	return dentry;
 }
 
+/* SMP-safe */
 struct dentry * lookup_one(const char * name, struct dentry * base)
 {
 	unsigned long hash;
@@ -715,6 +777,7 @@
  *
  * namei exists in two versions: namei/lnamei. The only difference is
  * that namei follows links, while lnamei does not.
+ * SMP-safe
  */
 int __user_walk(const char *name, unsigned flags, struct nameidata *nd)
 {
@@ -725,10 +788,8 @@
 	err = PTR_ERR(tmp);
 	if (!IS_ERR(tmp)) {
 		err = 0;
-		lock_kernel();
 		if (path_init(tmp, flags, nd))
 			err = path_walk(tmp, nd);
-		unlock_kernel();
 		putname(tmp);
 	}
 	return err;
@@ -771,8 +832,6 @@
 	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;
@@ -786,8 +845,6 @@
 			return -ENOTDIR;
 		if (IS_ROOT(victim))
 			return -EBUSY;
-		if (d_mountpoint(victim))
-			return -EBUSY;
 	} else if (S_ISDIR(victim->d_inode->i_mode))
 		return -EISDIR;
 	return 0;
@@ -848,7 +905,9 @@
 		goto exit_lock;
 
 	DQUOT_INIT(dir);
+	lock_kernel();
 	error = dir->i_op->create(dir, dentry, mode);
+	unlock_kernel();
 exit_lock:
 	up(&dir->i_zombie);
 	return error;
@@ -866,89 +925,99 @@
  *			  11 - read/write permissions needed
  * which is a lot more logical, and also allows the "no perm" needed
  * for symlinks (where the permissions are checked later).
+ * SMP-safe
  */
 int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
 {
 	int acc_mode, error = 0;
 	struct inode *inode;
 	struct dentry *dentry;
+	struct dentry *dir;
+	int count = 0;
 
 	acc_mode = ACC_MODE(flag);
+
+	/*
+	 * The simplest case - just a plain lookup.
+	 */
 	if (!(flag & O_CREAT)) {
 		if (path_init(pathname, lookup_flags(flag), nd))
 			error = path_walk(pathname, nd);
 		if (error)
 			return error;
-
 		dentry = nd->dentry;
-	} else {
-		struct dentry *dir;
+		goto ok;
+	}
 
-		if (path_init(pathname, LOOKUP_PARENT, nd))
-			error = path_walk(pathname, nd);
-		if (error)
-			return error;
-		/*
-		 * It's not obvious that open(".", O_CREAT, foo) should
-		 * fail, but it's even less obvious that it should succeed.
-		 * Since O_CREAT means an intention to create the thing and
-		 * open(2) had never created directories, count it as caller's
-		 * luserdom and let him sod off - -EISDIR it is.
-		 */
-		error = -EISDIR;
-		if (nd->last_type != LAST_NORM)
-			goto exit;
-		/* same for foo/ */
-		if (nd->last.name[nd->last.len])
-			goto exit;
+	/*
+	 * Create - we need to know the parent.
+	 */
+	if (path_init(pathname, LOOKUP_PARENT, nd))
+		error = path_walk(pathname, nd);
+	if (error)
+		return error;
 
-		dir = nd->dentry;
-		down(&dir->d_inode->i_sem);
+	/*
+	 * We have the parent and last component. First of all, check
+	 * that we are not asked to creat(2) an obvious directory - that
+	 * will not do.
+	 */
+	error = -EISDIR;
+	if (nd->last_type != LAST_NORM || nd->last.name[nd->last.len])
+		goto exit;
 
-		dentry = lookup_hash(&nd->last, nd->dentry);
-		error = PTR_ERR(dentry);
-		if (IS_ERR(dentry)) {
-			up(&dir->d_inode->i_sem);
+	dir = nd->dentry;
+	down(&dir->d_inode->i_sem);
+	dentry = lookup_hash(&nd->last, nd->dentry);
+
+do_last:
+	error = PTR_ERR(dentry);
+	if (IS_ERR(dentry)) {
+		up(&dir->d_inode->i_sem);
+		goto exit;
+	}
+
+	/* Negative dentry, just create the file */
+	if (!dentry->d_inode) {
+		error = vfs_create(dir->d_inode, dentry, mode);
+		up(&dir->d_inode->i_sem);
+		dput(nd->dentry);
+		nd->dentry = dentry;
+		if (error)
 			goto exit;
-		}
+		/* Don't check for write permission, don't truncate */
+		acc_mode = 0;
+		flag &= ~O_TRUNC;
+		goto ok;
+	}
 
-		if (dentry->d_inode) {
-			up(&dir->d_inode->i_sem);
-			error = -EEXIST;
-			if (flag & O_EXCL)
-				goto exit_dput;
-			if (dentry->d_inode->i_op &&
-			    dentry->d_inode->i_op->follow_link) {
-				/*
-				 * With O_EXCL it would be -EEXIST.
-				 * If symlink is a dangling one it's -ENOENT.
-				 * Otherwise we open the object it points to.
-				 */
-				error = do_follow_link(dentry, nd);
-				dput(dentry);
-				if (error)
-					return error;
-				dentry = nd->dentry;
-			} else {
-				dput(nd->dentry);
-				nd->dentry = dentry;
-			}
-			error = -EISDIR;
-			if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
-				goto exit;
-		} else {
-			error = vfs_create(dir->d_inode, dentry, mode);
-			up(&dir->d_inode->i_sem);
-			/* Don't check for write permission, don't truncate */
-			acc_mode = 0;
-			flag &= ~O_TRUNC;
-			dput(nd->dentry);
-			nd->dentry = dentry;
-			if (error)
-				goto exit;
-		}
+	/*
+	 * It already exists.
+	 */
+	up(&dir->d_inode->i_sem);
+
+	error = -EEXIST;
+	if (flag & O_EXCL)
+		goto exit_dput;
+
+	if (d_mountpoint(dentry)) {
+		error = -ELOOP;
+		if (flag & O_NOFOLLOW)
+			goto exit_dput;
+		do __follow_down(&nd->mnt,&dentry); while(d_mountpoint(dentry));
 	}
+	error = -ENOENT;
+	if (!dentry->d_inode)
+		goto exit_dput;
+	if (dentry->d_inode->i_op && dentry->d_inode->i_op->follow_link)
+		goto do_link;
 
+	dput(nd->dentry);
+	nd->dentry = dentry;
+	error = -EISDIR;
+	if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
+		goto exit;
+ok:
 	error = -ENOENT;
 	inode = dentry->d_inode;
 	if (!inode)
@@ -1023,8 +1092,50 @@
 exit:
 	path_release(nd);
 	return error;
+
+do_link:
+	error = -ELOOP;
+	if (flag & O_NOFOLLOW)
+		goto exit_dput;
+	/*
+	 * This is subtle. Instead of calling do_follow_link() we do the
+	 * thing by hands. The reason is that this way we have zero link_count
+	 * and path_walk() (called from ->follow_link) honoring LOOKUP_PARENT.
+	 * After that we have the parent and last component, i.e.
+	 * we are in the same situation as after the first path_walk().
+	 * Well, almost - if the last component is normal we get its copy
+	 * stored in nd->last.name and we will have to putname() it when we
+	 * are done. Procfs-like symlinks just set LAST_BIND.
+	 */
+	UPDATE_ATIME(dentry->d_inode);
+	error = dentry->d_inode->i_op->follow_link(dentry, nd);
+	dput(dentry);
+	if (error)
+		return error;
+	if (nd->last_type == LAST_BIND) {
+		dentry = nd->dentry;
+		goto ok;
+	}
+	error = -EISDIR;
+	if (nd->last_type != LAST_NORM)
+		goto exit;
+	if (nd->last.name[nd->last.len]) {
+		putname(nd->last.name);
+		goto exit;
+	}
+	if (count++==32) {
+		dentry = nd->dentry;
+		putname(nd->last.name);
+		goto ok;
+	}
+	dir = nd->dentry;
+	down(&dir->d_inode->i_sem);
+	dentry = lookup_hash(&nd->last, nd->dentry);
+	putname(nd->last.name);
+	goto do_last;
 }
 
+/* SMP-safe */
 static struct dentry *lookup_create(struct nameidata *nd, int is_dir)
 {
 	struct dentry *dentry;
@@ -1065,7 +1176,9 @@
 		goto exit_lock;
 
 	DQUOT_INIT(dir);
+	lock_kernel();
 	error = dir->i_op->mknod(dir, dentry, mode, dev);
+	unlock_kernel();
 exit_lock:
 	up(&dir->i_zombie);
 	return error;
@@ -1084,7 +1197,6 @@
 	if (IS_ERR(tmp))
 		return PTR_ERR(tmp);
 
-	lock_kernel();
 	if (path_init(tmp, LOOKUP_PARENT, &nd))
 		error = path_walk(tmp, &nd);
 	if (error)
@@ -1110,7 +1222,6 @@
 	up(&nd.dentry->d_inode->i_sem);
 	path_release(&nd);
 out:
-	unlock_kernel();
 	putname(tmp);
 
 	return error;
@@ -1131,7 +1242,9 @@
 
 	DQUOT_INIT(dir);
 	mode &= (S_IRWXUGO|S_ISVTX) & ~current->fs->umask;
+	lock_kernel();
 	error = dir->i_op->mkdir(dir, dentry, mode);
+	unlock_kernel();
 
 exit_lock:
 	up(&dir->i_zombie);
@@ -1149,7 +1262,6 @@
 		struct dentry *dentry;
 		struct nameidata nd;
 
-		lock_kernel();
 		if (path_init(tmp, LOOKUP_PARENT, &nd))
 			error = path_walk(tmp, &nd);
 		if (error)
@@ -1163,7 +1275,6 @@
 		up(&nd.dentry->d_inode->i_sem);
 		path_release(&nd);
 out:
-		unlock_kernel();
 		putname(tmp);
 	}
 
@@ -1188,10 +1299,10 @@
 static void d_unhash(struct dentry *dentry)
 {
 	dget(dentry);
-	switch (dentry->d_count) {
+	switch (atomic_read(&dentry->d_count)) {
 	default:
 		shrink_dcache_parent(dentry);
-		if (dentry->d_count != 2)
+		if (atomic_read(&dentry->d_count) != 2)
 			break;
 	case 2:
 		d_drop(dentry);
@@ -1213,9 +1324,17 @@
 
 	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;
+	if (IS_DEADDIR(dir))
+		error = -ENOENT;
+	else if (d_mountpoint(dentry))
+		error = -EBUSY;
+	else {
+		lock_kernel();
+		error = dir->i_op->rmdir(dir, dentry);
+		unlock_kernel();
+		if (!error)
+			dentry->d_inode->i_flags |= S_DEAD;
+	}
 	double_up(&dir->i_zombie, &dentry->d_inode->i_zombie);
 	if (!error)
 		d_delete(dentry);
@@ -1234,7 +1353,6 @@
 	name = getname(pathname);
 	if(IS_ERR(name))
 		return PTR_ERR(name);
-	lock_kernel();
 
 	if (path_init(name, LOOKUP_PARENT, &nd))
 		error = path_walk(name, &nd);
@@ -1260,7 +1378,6 @@
 exit1:
 	path_release(&nd);
 exit:
-	unlock_kernel();
 	putname(name);
 	return error;
 }
@@ -1275,9 +1392,15 @@
 		error = -EPERM;
 		if (dir->i_op && dir->i_op->unlink) {
 			DQUOT_INIT(dir);
-			error = dir->i_op->unlink(dir, dentry);
-			if (!error)
-				d_delete(dentry);
+			if (d_mountpoint(dentry))
+				error = -EBUSY;
+			else {
+				lock_kernel();
+				error = dir->i_op->unlink(dir, dentry);
+				unlock_kernel();
+				if (!error)
+					d_delete(dentry);
+			}
 		}
 	}
 	up(&dir->i_zombie);
@@ -1294,7 +1417,6 @@
 	name = getname(pathname);
 	if(IS_ERR(name))
 		return PTR_ERR(name);
-	lock_kernel();
 
 	if (path_init(name, LOOKUP_PARENT, &nd))
 		error = path_walk(name, &nd);
@@ -1318,7 +1440,6 @@
 exit1:
 	path_release(&nd);
 exit:
-	unlock_kernel();
 	putname(name);
 
 	return error;
@@ -1343,7 +1464,9 @@
 		goto exit_lock;
 
 	DQUOT_INIT(dir);
+	lock_kernel();
 	error = dir->i_op->symlink(dir, dentry, oldname);
+	unlock_kernel();
 
 exit_lock:
 	up(&dir->i_zombie);
@@ -1365,7 +1488,6 @@
 		struct dentry *dentry;
 		struct nameidata nd;
 
-		lock_kernel();
 		if (path_init(to, LOOKUP_PARENT, &nd))
 			error = path_walk(to, &nd);
 		if (error)
@@ -1379,7 +1501,6 @@
 		up(&nd.dentry->d_inode->i_sem);
 		path_release(&nd);
 out:
-		unlock_kernel();
 		putname(to);
 	}
 	putname(from);
@@ -1415,7 +1536,9 @@
 		goto exit_lock;
 
 	DQUOT_INIT(dir);
+	lock_kernel();
 	error = dir->i_op->link(old_dentry, dir, new_dentry);
+	unlock_kernel();
 
 exit_lock:
 	up(&dir->i_zombie);
@@ -1446,7 +1569,6 @@
 		struct dentry *new_dentry;
 		struct nameidata nd, old_nd;
 
-		lock_kernel();
 		error = 0;
 		if (path_init(from, LOOKUP_POSITIVE, &old_nd))
 			error = path_walk(from, &old_nd);
@@ -1470,7 +1592,6 @@
 out:
 		path_release(&old_nd);
 exit:
-		unlock_kernel();
 		putname(to);
 	}
 	putname(from);
@@ -1555,7 +1676,12 @@
 	} else
 		double_down(&old_dir->i_zombie,
 			    &new_dir->i_zombie);
-	error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+	if (IS_DEADDIR(old_dir)||IS_DEADDIR(new_dir))
+		error = -ENOENT;
+	else if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
+		error = -EBUSY;
+	else 
+		error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
 	if (target) {
 		if (!error)
 			target->i_flags |= S_DEAD;
@@ -1603,7 +1729,10 @@
 	DQUOT_INIT(old_dir);
 	DQUOT_INIT(new_dir);
 	double_down(&old_dir->i_zombie, &new_dir->i_zombie);
-	error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
+	if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
+		error = -EBUSY;
+	else
+		error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
 	double_up(&old_dir->i_zombie, &new_dir->i_zombie);
 	if (error)
 		return error;
@@ -1677,8 +1806,10 @@
 	if (IS_ERR(new_dentry))
 		goto exit4;
 
+	lock_kernel();
 	error = vfs_rename(old_dir->d_inode, old_dentry,
 				   new_dir->d_inode, new_dentry);
+	unlock_kernel();
 
 	dput(new_dentry);
 exit4:
@@ -1705,9 +1836,7 @@
 	to = getname(newname);
 	error = PTR_ERR(to);
 	if (!IS_ERR(to)) {
-		lock_kernel();
 		error = do_rename(from,to);
-		unlock_kernel();
 		putname(to);
 	}
 	putname(from);
@@ -1734,6 +1863,8 @@
 static inline int
 __vfs_follow_link(struct nameidata *nd, const char *link)
 {
+	int res = 0;
+	char *name;
 	if (IS_ERR(link))
 		goto fail;
 
@@ -1741,10 +1872,25 @@
 		path_release(nd);
 		if (!walk_init_root(link, nd))
 			/* weird __emul_prefix() stuff did it */
-			return 0;
+			goto out;
 	}
-	return path_walk(link, nd);
-
+	res = path_walk(link, nd);
+out:
+	if (current->link_count || res || nd->last_type!=LAST_NORM)
+		return res;
+	/*
+	 * If it is an iterative symlinks resolution in open_namei() we
+	 * have to copy the last component. And all that crap because of
+	 * bloody create() on broken symlinks. Furrfu...
+	 */
+	name = __getname();
+	if (IS_ERR(name))
+		goto fail_name;
+	strcpy(name, nd->last.name);
+	nd->last.name = name;
+	return 0;
+fail_name:
+	link = name;
 fail:
 	path_release(nd);
 	return PTR_ERR(link);
@@ -1761,7 +1907,7 @@
 	struct page * page;
 	struct address_space *mapping = dentry->d_inode->i_mapping;
 	page = read_cache_page(mapping, 0, (filler_t *)mapping->a_ops->readpage,
-				dentry);
+				NULL);
 	if (IS_ERR(page))
 		goto sync_fail;
 	wait_on_page(page);
@@ -1806,3 +1952,20 @@
 	readlink:	page_readlink,
 	follow_link:	page_follow_link,
 };
+
+/* SLAB cache for name blocks */
+kmem_cache_t *names_cachep;
+
+static int __init namecache_init(void)
+{
+	names_cachep = kmem_cache_create("names_cache",
+			PAGE_SIZE,
+			0,
+			SLAB_HWCACHE_ALIGN,
+			NULL, NULL);
+	if (!names_cachep)
+		panic("Cannot create names cache");
+	return 0;
+}
+
+module_init(namecache_init)

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)