summaryrefslogtreecommitdiffstats
path: root/fs/namei.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/namei.c')
-rw-r--r--fs/namei.c393
1 files changed, 220 insertions, 173 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 7a94b38dd..67f8c0a18 100644
--- a/fs/namei.c
+++ b/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 permission(struct inode * inode,int mask)
{
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 @@ void put_write_access(struct inode * inode)
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 @@ static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd)
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 @@ static inline int follow_down(struct dentry ** dentry, struct vfsmount **mnt)
*
* 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 @@ int walk_name(const char * name, struct nameidata *nd)
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 @@ int walk_name(const char * name, struct nameidata *nd)
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 @@ last_component:
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 @@ last_component:
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 @@ out_dput:
dput(dentry);
break;
}
- dput(nd->dentry);
- mntput(nd->mnt);
+ path_release(nd);
return_err:
return err;
}
@@ -491,7 +566,7 @@ static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
{
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 @@ static int __emul_lookup_dentry(const char *name, struct nameidata *nd)
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 @@ void set_fs_altroot(void)
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 @@ walk_init_root(const char *name, struct nameidata *nd)
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 @@ int walk_init(const char *name,unsigned int flags,struct nameidata *nd)
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 @@ access:
* 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 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
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 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
} 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 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
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 @@ exit_lock:
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 @@ asmlinkage long sys_mknod(const char * filename, int mode, dev_t dev)
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 @@ exit_lock:
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 @@ asmlinkage long sys_rmdir(const char * pathname)
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 @@ asmlinkage long sys_rmdir(const char * pathname)
}
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 @@ asmlinkage long sys_unlink(const char * pathname)
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 @@ asmlinkage long sys_unlink(const char * pathname)
}
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 @@ exit_lock:
asmlinkage long sys_symlink(const char * oldname, const char * newname)
{
- int error;
+ int error = 0;
char * from;
char * to;
@@ -1323,18 +1360,23 @@ asmlinkage long sys_symlink(const char * oldname, const char * newname)
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 @@ asmlinkage long sys_link(const char * oldname, const char * newname)
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 @@ static inline int do_rename(const char * oldname, const char * newname)
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 @@ exit4:
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 @@ __vfs_follow_link(struct nameidata *nd, const char *link)
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);
}