patch-2.3.99-pre7 linux/fs/namei.c

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

diff -u --recursive --new-file v2.3.99-pre6/linux/fs/namei.c linux/fs/namei.c
@@ -11,6 +11,8 @@
 /* [Feb 1997 T. Schoebel-Theuer] Complete rewrite of the pathname
  * lookup logic.
  */
+/* [Feb-Apr 2000, AV] Rewrite to the new namespace architecture.
+ */
 
 #include <linux/mm.h>
 #include <linux/proc_fs.h>
@@ -27,11 +29,6 @@
 
 #include <asm/namei.h>
 
-/* This can be removed after the beta phase. */
-#define CACHE_SUPERVISE	/* debug the correctness of dcache entries */
-#undef DEBUG		/* some other debugging */
-
-
 #define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
 
 /* [Feb-1997 T. Schoebel-Theuer]
@@ -85,6 +82,15 @@
  * [10-Sep-98 Alan Modra] Another symlink change.
  */
 
+/* [Feb-Apr 2000 AV] Complete rewrite. Rules for symlinks:
+ *	inside the path - always follow.
+ *	in the last component in creation/removal/renaming - never follow.
+ *	if LOOKUP_FOLLOW passed - follow.
+ *	if the pathname has trailing slashes - follow.
+ *	otherwise - don't follow.
+ * (applied in that order).
+ */
+
 /* In order to reduce some races, while at the same time doing additional
  * checking and hopefully speeding things up, we copy filenames to the
  * kernel data space before using them..
@@ -142,24 +148,35 @@
 {
 	int mode = inode->i_mode;
 
-	if (inode->i_op && inode->i_op->permission)
-		return inode->i_op->permission(inode, mask);
-	else if ((mask & S_IWOTH) && IS_RDONLY(inode) &&
+	if (inode->i_op && inode->i_op->permission) {
+		int retval;
+		lock_kernel();
+		retval = inode->i_op->permission(inode, mask);
+		unlock_kernel();
+		return retval;
+	}
+
+	if ((mask & S_IWOTH) && IS_RDONLY(inode) &&
 		 (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
 		return -EROFS; /* Nobody gets write access to a read-only fs */
-	else if ((mask & S_IWOTH) && IS_IMMUTABLE(inode))
+
+	if ((mask & S_IWOTH) && IS_IMMUTABLE(inode))
 		return -EACCES; /* Nobody gets write access to an immutable file */
-	else if (current->fsuid == inode->i_uid)
+
+	if (current->fsuid == inode->i_uid)
 		mode >>= 6;
 	else if (in_group_p(inode->i_gid))
 		mode >>= 3;
+
 	if (((mode & mask & S_IRWXO) == mask) || capable(CAP_DAC_OVERRIDE))
 		return 0;
+
 	/* read and search access */
 	if ((mask == S_IROTH) ||
 	    (S_ISDIR(mode)  && !(mask & ~(S_IROTH | S_IXOTH))))
 		if (capable(CAP_DAC_READ_SEARCH))
 			return 0;
+
 	return -EACCES;
 }
 
@@ -191,6 +208,14 @@
 	atomic_dec(&inode->i_writecount);
 }
 
+void path_release(struct nameidata *nd)
+{
+	lock_kernel();
+	dput(nd->dentry);
+	mntput(nd->mnt);
+	unlock_kernel();
+}
+
 /*
  * Internal lookup() using the new generic dcache.
  */
@@ -265,19 +290,54 @@
 	current->link_count--;
 	return err;
 loop:
-	dput(nd->dentry);
-	mntput(nd->mnt);
+	path_release(nd);
 	return -ELOOP;
 }
 
-static inline int follow_down(struct dentry ** dentry, struct vfsmount **mnt)
+static inline int __follow_up(struct vfsmount **mnt, struct dentry **base)
 {
-	struct dentry * parent = dget((*dentry)->d_mounts);
-	dput(*dentry);
-	*dentry = parent;
+	struct vfsmount *parent=(*mnt)->mnt_parent;
+	struct dentry *dentry;
+	if (parent == *mnt)
+		return 0;
+	mntget(parent);
+	dentry=dget((*mnt)->mnt_mountpoint);
+	dput(*base);
+	*base = dentry;
+	mntput(*mnt);
+	*mnt = parent;
 	return 1;
 }
 
+int follow_up(struct vfsmount **mnt, struct dentry **dentry)
+{
+	return __follow_up(mnt, dentry);
+}
+
+static inline int __follow_down(struct vfsmount **mnt, struct dentry **dentry)
+{
+	struct list_head *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);
+			mntput(tmp->mnt_parent);
+			/* tmp holds the mountpoint, so... */
+			dput(*dentry);
+			*dentry = dget(tmp->mnt_root);
+			return 1;
+		}
+		p = p->next;
+	}
+	return 0;
+}
+
+int follow_down(struct vfsmount **mnt, struct dentry **dentry)
+{
+	return __follow_down(mnt,dentry);
+}
+
 /*
  * Name resolution.
  *
@@ -286,7 +346,7 @@
  *
  * We expect 'base' to be positive and a directory.
  */
-int walk_name(const char * name, struct nameidata *nd)
+int path_walk(const char * name, struct nameidata *nd)
 {
 	struct dentry *dentry;
 	struct inode *inode;
@@ -343,12 +403,20 @@
 			case 2:	
 				if (this.name[1] != '.')
 					break;
-				if (nd->dentry != current->fs->root) {
-					dentry = dget(nd->dentry->d_covers->d_parent);
-					dput(nd->dentry);
-					nd->dentry = dentry;
-					inode = dentry->d_inode;
+				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;
 				}
+				inode = nd->dentry->d_inode;
 				/* fallthrough */
 			case 1:
 				continue;
@@ -371,7 +439,7 @@
 				break;
 		}
 		/* Check mountpoints.. */
-		while (d_mountpoint(dentry) && follow_down(&dentry, &nd->mnt))
+		while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
 			;
 
 		err = -ENOENT;
@@ -415,12 +483,20 @@
 			case 2:	
 				if (this.name[1] != '.')
 					break;
-				if (nd->dentry != current->fs->root) {
-					dentry = dget(nd->dentry->d_covers->d_parent);
-					dput(nd->dentry);
-					nd->dentry = dentry;
-					inode = dentry->d_inode;
+				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;
 				}
+				inode = nd->dentry->d_inode;
 				/* fallthrough */
 			case 1:
 				goto return_base;
@@ -437,7 +513,7 @@
 			if (IS_ERR(dentry))
 				break;
 		}
-		while (d_mountpoint(dentry) && follow_down(&dentry, &nd->mnt))
+		while (d_mountpoint(dentry) && __follow_down(&nd->mnt, &dentry))
 			;
 		inode = dentry->d_inode;
 		if ((lookup_flags & LOOKUP_FOLLOW)
@@ -480,8 +556,7 @@
 		dput(dentry);
 		break;
 	}
-	dput(nd->dentry);
-	mntput(nd->mnt);
+	path_release(nd);
 return_err:
 	return err;
 }
@@ -491,7 +566,7 @@
 {
 	nd->mnt = mntget(current->fs->altrootmnt);
 	nd->dentry = dget(current->fs->altroot);
-	if (walk_name(name, nd))
+	if (path_walk(name, nd))
 		return 0;
 
 	if (!nd->dentry->d_inode) {
@@ -500,18 +575,16 @@
 		nd_root.flags = nd->flags;
 		nd_root.mnt = mntget(current->fs->rootmnt);
 		nd_root.dentry = dget(current->fs->root);
-		if (walk_name(name, &nd_root))
+		if (path_walk(name, &nd_root))
 			return 1;
 		if (nd_root.dentry->d_inode) {
-			dput(nd->dentry);
-			mntput(nd->mnt);
+			path_release(nd);
 			nd->dentry = nd_root.dentry;
 			nd->mnt = nd_root.mnt;
 			nd->last = nd_root.last;
 			return 1;
 		}
-		dput(nd_root.dentry);
-		mntput(nd_root.mnt);
+		path_release(&nd_root);
 	}
 	return 1;
 }
@@ -526,7 +599,7 @@
 		nd.mnt = mntget(current->fs->rootmnt);
 		nd.dentry = dget(current->fs->root);
 		nd.flags = LOOKUP_FOLLOW|LOOKUP_DIRECTORY|LOOKUP_POSITIVE;
-		if (walk_name(emul,&nd) == 0) {
+		if (path_walk(emul,&nd) == 0) {
 			mnt = nd.mnt;
 			dentry = nd.dentry;
 		}
@@ -552,7 +625,7 @@
 	return 1;
 }
 
-int walk_init(const char *name,unsigned int flags,struct nameidata *nd)
+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;
@@ -563,25 +636,11 @@
 	return 1;
 }
 
-struct dentry * lookup_dentry(const char * name, unsigned int lookup_flags)
-{
-	struct nameidata nd;
-	int err = 0;
-
-	if (walk_init(name, lookup_flags, &nd))
-		err = walk_name(name, &nd);
-	if (!err) {
-		mntput(nd.mnt);
-		return nd.dentry;
-	}
-	return ERR_PTR(err);
-}
-
 /*
  * Restricted form of lookup. Doesn't follow links, single-component only,
  * needs parent already locked. Doesn't follow mounts.
  */
-static inline struct dentry * lookup_hash(struct qstr *name, struct dentry * base)
+struct dentry * lookup_hash(struct qstr *name, struct dentry * base)
 {
 	struct dentry * dentry;
 	struct inode *inode;
@@ -657,18 +716,22 @@
  * namei exists in two versions: namei/lnamei. The only difference is
  * that namei follows links, while lnamei does not.
  */
-struct dentry * __namei(const char *pathname, unsigned int lookup_flags)
+int __user_walk(const char *name, unsigned flags, struct nameidata *nd)
 {
-	char *name;
-	struct dentry *dentry;
+	char *tmp;
+	int err;
 
-	name = getname(pathname);
-	dentry = (struct dentry *) name;
-	if (!IS_ERR(name)) {
-		dentry = lookup_dentry(name,lookup_flags|LOOKUP_POSITIVE);
-		putname(name);
+	tmp = getname(name);
+	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 dentry;
+	return err;
 }
 
 /*
@@ -812,8 +875,8 @@
 
 	acc_mode = ACC_MODE(flag);
 	if (!(flag & O_CREAT)) {
-		if (walk_init(pathname, lookup_flags(flag), nd))
-			error = walk_name(pathname, nd);
+		if (path_init(pathname, lookup_flags(flag), nd))
+			error = path_walk(pathname, nd);
 		if (error)
 			return error;
 
@@ -821,8 +884,8 @@
 	} else {
 		struct dentry *dir;
 
-		if (walk_init(pathname, LOOKUP_PARENT, nd))
-			error = walk_name(pathname, nd);
+		if (path_init(pathname, LOOKUP_PARENT, nd))
+			error = path_walk(pathname, nd);
 		if (error)
 			return error;
 		/*
@@ -960,41 +1023,29 @@
 exit_dput:
 	dput(dentry);
 exit:
-	dput(nd->dentry);
-	mntput(nd->mnt);
+	path_release(nd);
 	return error;
 }
 
-static struct dentry *lookup_create(const char *name, int is_dir)
+static struct dentry *lookup_create(struct nameidata *nd, int is_dir)
 {
-	struct nameidata nd;
 	struct dentry *dentry;
-	int err = 0;
-	if (walk_init(name, LOOKUP_PARENT, &nd))
-		err = walk_name(name, &nd);
-	dentry = ERR_PTR(err);
-	if (err)
-		goto out;
-	down(&nd.dentry->d_inode->i_sem);
+
+	down(&nd->dentry->d_inode->i_sem);
 	dentry = ERR_PTR(-EEXIST);
-	if (nd.last_type != LAST_NORM)
+	if (nd->last_type != LAST_NORM)
 		goto fail;
-	dentry = lookup_hash(&nd.last, nd.dentry);
+	dentry = lookup_hash(&nd->last, nd->dentry);
 	if (IS_ERR(dentry))
 		goto fail;
-	if (!is_dir && nd.last.name[nd.last.len] && !dentry->d_inode)
+	if (!is_dir && nd->last.name[nd->last.len] && !dentry->d_inode)
 		goto enoent;
-out_dput:
-	dput(nd.dentry);
-	mntput(nd.mnt);
-out:
 	return dentry;
 enoent:
 	dput(dentry);
 	dentry = ERR_PTR(-ENOENT);
 fail:
-	up(&nd.dentry->d_inode->i_sem);
-	goto out_dput;
+	return dentry;
 }
 
 int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
@@ -1022,33 +1073,12 @@
 	return error;
 }
 
-struct dentry * do_mknod(const char * filename, int mode, dev_t dev)
-{
-	int error;
-	struct dentry *dir;
-	struct dentry *dentry, *retval;
-
-	dentry = lookup_create(filename, 0);
-	if (IS_ERR(dentry))
-		return dentry;
-
-	dir = dget(dentry->d_parent);
-
-	error = vfs_mknod(dir->d_inode, dentry, mode, dev);
-
-	retval = ERR_PTR(error);
-	if (!error)
-		retval = dget(dentry);
-	unlock_dir(dir);
-	dput(dentry);
-	return retval;
-}
-
 asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev)
 {
-	int error;
+	int error = 0;
 	char * tmp;
-	struct dentry * dentry, *dir;
+	struct dentry * dentry;
+	struct nameidata nd;
 
 	if (S_ISDIR(mode))
 		return -EPERM;
@@ -1057,26 +1087,30 @@
 		return PTR_ERR(tmp);
 
 	lock_kernel();
-	dentry = lookup_create(tmp, 0);
-	error = PTR_ERR(dentry);
-	if (IS_ERR(dentry))
+	if (path_init(tmp, LOOKUP_PARENT, &nd))
+		error = path_walk(tmp, &nd);
+	if (error)
 		goto out;
-	dir = dget(dentry->d_parent);
-	switch (mode & S_IFMT) {
-	case 0: case S_IFREG:
-		error = vfs_create(dir->d_inode, dentry, mode);
-		break;
-	case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK:
-		error = vfs_mknod(dir->d_inode, dentry, mode, dev);
-		break;
-	case S_IFDIR:
-		error = -EPERM;
-		break;
-	default:
-		error = -EINVAL;
+	dentry = lookup_create(&nd, 0);
+	error = PTR_ERR(dentry);
+	if (!IS_ERR(dentry)) {
+		switch (mode & S_IFMT) {
+		case 0: case S_IFREG:
+			error = vfs_create(nd.dentry->d_inode,dentry,mode);
+			break;
+		case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK:
+			error = vfs_mknod(nd.dentry->d_inode,dentry,mode,dev);
+			break;
+		case S_IFDIR:
+			error = -EPERM;
+			break;
+		default:
+			error = -EINVAL;
+		}
+		dput(dentry);
 	}
-	unlock_dir(dir);
-	dput(dentry);
+	up(&nd.dentry->d_inode->i_sem);
+	path_release(&nd);
 out:
 	unlock_kernel();
 	putname(tmp);
@@ -1108,27 +1142,32 @@
 
 asmlinkage long sys_mkdir(const char * pathname, int mode)
 {
-	int error;
+	int error = 0;
 	char * tmp;
 
 	tmp = getname(pathname);
 	error = PTR_ERR(tmp);
 	if (!IS_ERR(tmp)) {
-		struct dentry *dir;
 		struct dentry *dentry;
+		struct nameidata nd;
 
 		lock_kernel();
-		dentry = lookup_create(tmp, 1);
+		if (path_init(tmp, LOOKUP_PARENT, &nd))
+			error = path_walk(tmp, &nd);
+		if (error)
+			goto out;
+		dentry = lookup_create(&nd, 1);
 		error = PTR_ERR(dentry);
 		if (!IS_ERR(dentry)) {
-			dir = dget(dentry->d_parent);
-			error = vfs_mkdir(dir->d_inode, dentry, mode);
-			unlock_dir(dir);
+			error = vfs_mkdir(nd.dentry->d_inode, dentry, mode);
 			dput(dentry);
 		}
+		up(&nd.dentry->d_inode->i_sem);
+		path_release(&nd);
+out:
 		unlock_kernel();
+		putname(tmp);
 	}
-	putname(tmp);
 
 	return error;
 }
@@ -1197,8 +1236,8 @@
 		return PTR_ERR(name);
 	lock_kernel();
 
-	if (walk_init(name, LOOKUP_PARENT, &nd))
-		error = walk_name(name, &nd);
+	if (path_init(name, LOOKUP_PARENT, &nd))
+		error = path_walk(name, &nd);
 	if (error)
 		goto exit;
 
@@ -1219,8 +1258,7 @@
 	}
 	up(&nd.dentry->d_inode->i_sem);
 exit1:
-	dput(nd.dentry);
-	mntput(nd.mnt);
+	path_release(&nd);
 exit:
 	unlock_kernel();
 	putname(name);
@@ -1256,8 +1294,8 @@
 		return PTR_ERR(name);
 	lock_kernel();
 
-	if (walk_init(name, LOOKUP_PARENT, &nd))
-		error = walk_name(name, &nd);
+	if (path_init(name, LOOKUP_PARENT, &nd))
+		error = path_walk(name, &nd);
 	if (error)
 		goto exit;
 	error = -EISDIR;
@@ -1276,8 +1314,7 @@
 	}
 	up(&nd.dentry->d_inode->i_sem);
 exit1:
-	dput(nd.dentry);
-	mntput(nd.mnt);
+	path_release(&nd);
 exit:
 	unlock_kernel();
 	putname(name);
@@ -1313,7 +1350,7 @@
 
 asmlinkage long sys_symlink(const char * oldname, const char * newname)
 {
-	int error;
+	int error = 0;
 	char * from;
 	char * to;
 
@@ -1323,18 +1360,23 @@
 	to = getname(newname);
 	error = PTR_ERR(to);
 	if (!IS_ERR(to)) {
-		struct dentry *dir;
 		struct dentry *dentry;
+		struct nameidata nd;
 
 		lock_kernel();
-		dentry = lookup_create(to, 0);
+		if (path_init(to, LOOKUP_PARENT, &nd))
+			error = path_walk(to, &nd);
+		if (error)
+			goto out;
+		dentry = lookup_create(&nd, 0);
 		error = PTR_ERR(dentry);
 		if (!IS_ERR(dentry)) {
-			dir = dget(dentry->d_parent);
-			error = vfs_symlink(dir->d_inode, dentry, from);
-			unlock_dir(dir);
+			error = vfs_symlink(nd.dentry->d_inode, dentry, from);
 			dput(dentry);
 		}
+		up(&nd.dentry->d_inode->i_sem);
+		path_release(&nd);
+out:
 		unlock_kernel();
 		putname(to);
 	}
@@ -1399,23 +1441,32 @@
 	to = getname(newname);
 	error = PTR_ERR(to);
 	if (!IS_ERR(to)) {
-		struct dentry *old_dentry, *new_dentry, *dir;
+		struct dentry *new_dentry;
+		struct nameidata nd, old_nd;
 
 		lock_kernel();
-		old_dentry = lookup_dentry(from, LOOKUP_POSITIVE);
-		error = PTR_ERR(old_dentry);
-		if (IS_ERR(old_dentry))
+		error = 0;
+		if (path_init(from, LOOKUP_POSITIVE, &old_nd))
+			error = path_walk(from, &old_nd);
+		if (error)
 			goto exit;
-
-		new_dentry = lookup_create(to, 0);
+		if (path_init(to, LOOKUP_PARENT, &nd))
+			error = path_walk(to, &nd);
+		if (error)
+			goto out;
+		error = -EXDEV;
+		if (old_nd.mnt != nd.mnt)
+			goto out;
+		new_dentry = lookup_create(&nd, 0);
 		error = PTR_ERR(new_dentry);
 		if (!IS_ERR(new_dentry)) {
-			dir = dget(new_dentry->d_parent);
-			error = vfs_link(old_dentry, dir->d_inode, new_dentry);
-			unlock_dir(dir);
+			error = vfs_link(old_nd.dentry, nd.dentry->d_inode, new_dentry);
 			dput(new_dentry);
 		}
-		dput(old_dentry);
+		up(&nd.dentry->d_inode->i_sem);
+		path_release(&nd);
+out:
+		path_release(&old_nd);
 exit:
 		unlock_kernel();
 		putname(to);
@@ -1577,14 +1628,14 @@
 	struct dentry * old_dentry, *new_dentry;
 	struct nameidata oldnd, newnd;
 
-	if (walk_init(oldname, LOOKUP_PARENT, &oldnd))
-		error = walk_name(oldname, &oldnd);
+	if (path_init(oldname, LOOKUP_PARENT, &oldnd))
+		error = path_walk(oldname, &oldnd);
 
 	if (error)
 		goto exit;
 
-	if (walk_init(newname, LOOKUP_PARENT, &newnd))
-		error = walk_name(newname, &newnd);
+	if (path_init(newname, LOOKUP_PARENT, &newnd))
+		error = path_walk(newname, &newnd);
 	if (error)
 		goto exit1;
 
@@ -1633,11 +1684,9 @@
 exit3:
 	double_up(&new_dir->d_inode->i_sem, &old_dir->d_inode->i_sem);
 exit2:
-	dput(newnd.dentry);
-	mntput(newnd.mnt);
+	path_release(&newnd);
 exit1:
-	dput(oldnd.dentry);
-	mntput(oldnd.mnt);
+	path_release(&oldnd);
 exit:
 	return error;
 }
@@ -1687,17 +1736,15 @@
 		goto fail;
 
 	if (*link == '/') {
-		dput(nd->dentry);
-		mntput(nd->mnt);
+		path_release(nd);
 		if (!walk_init_root(link, nd))
 			/* weird __emul_prefix() stuff did it */
 			return 0;
 	}
-	return walk_name(link, nd);
+	return path_walk(link, nd);
 
 fail:
-	dput(nd->dentry);
-	mntput(nd->mnt);
+	path_release(nd);
 	return PTR_ERR(link);
 }
 

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