Merge git://oss.sgi.com:8090/oss/git/rc-fixes-xfs-2.6
[pandora-kernel.git] / fs / namei.c
index 7bde381..e28de84 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/audit.h>
 #include <linux/capability.h>
 #include <linux/file.h>
+#include <linux/fcntl.h>
+#include <linux/namei.h>
 #include <asm/namei.h>
 #include <asm/uaccess.h>
 
@@ -486,7 +488,7 @@ static struct dentry * real_lookup(struct dentry * parent, struct qstr * name, s
 static int __emul_lookup_dentry(const char *, struct nameidata *);
 
 /* SMP-safe */
-static inline int
+static __always_inline int
 walk_init_root(const char *name, struct nameidata *nd)
 {
        read_lock(&current->fs->lock);
@@ -504,7 +506,7 @@ walk_init_root(const char *name, struct nameidata *nd)
        return 1;
 }
 
-static inline int __vfs_follow_link(struct nameidata *nd, const char *link)
+static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
 {
        int res = 0;
        char *name;
@@ -544,7 +546,7 @@ struct path {
        struct dentry *dentry;
 };
 
-static inline int __do_follow_link(struct path *path, struct nameidata *nd)
+static __always_inline int __do_follow_link(struct path *path, struct nameidata *nd)
 {
        int error;
        void *cookie;
@@ -690,7 +692,7 @@ int follow_down(struct vfsmount **mnt, struct dentry **dentry)
        return 0;
 }
 
-static inline void follow_dotdot(struct nameidata *nd)
+static __always_inline void follow_dotdot(struct nameidata *nd)
 {
        while(1) {
                struct vfsmount *parent;
@@ -788,7 +790,7 @@ static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
 
        inode = nd->dentry->d_inode;
        if (nd->depth)
-               lookup_flags = LOOKUP_FOLLOW;
+               lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);
 
        /* At this point we know we have a real path component. */
        for(;;) {
@@ -883,7 +885,8 @@ static fastcall int __link_path_walk(const char * name, struct nameidata *nd)
 last_with_slashes:
                lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 last_component:
-               nd->flags &= ~LOOKUP_CONTINUE;
+               /* Clear LOOKUP_CONTINUE iff it was previously unset */
+               nd->flags &= lookup_flags | ~LOOKUP_CONTINUE;
                if (lookup_flags & LOOKUP_PARENT)
                        goto lookup_parent;
                if (this.name[0] == '.') switch (this.len) {
@@ -1063,9 +1066,12 @@ set_it:
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
+static int fastcall do_path_lookup(int dfd, const char *name,
+                               unsigned int flags, struct nameidata *nd)
 {
        int retval = 0;
+       int fput_needed;
+       struct file *file;
 
        nd->last_type = LAST_ROOT; /* if there are only slashes... */
        nd->flags = flags;
@@ -1083,22 +1089,59 @@ int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata
                }
                nd->mnt = mntget(current->fs->rootmnt);
                nd->dentry = dget(current->fs->root);
-       } else {
+       } else if (dfd == AT_FDCWD) {
                nd->mnt = mntget(current->fs->pwdmnt);
                nd->dentry = dget(current->fs->pwd);
+       } else {
+               struct dentry *dentry;
+
+               file = fget_light(dfd, &fput_needed);
+               retval = -EBADF;
+               if (!file)
+                       goto unlock_fail;
+
+               dentry = file->f_dentry;
+
+               retval = -ENOTDIR;
+               if (!S_ISDIR(dentry->d_inode->i_mode))
+                       goto fput_unlock_fail;
+
+               retval = file_permission(file, MAY_EXEC);
+               if (retval)
+                       goto fput_unlock_fail;
+
+               nd->mnt = mntget(file->f_vfsmnt);
+               nd->dentry = dget(dentry);
+
+               fput_light(file, fput_needed);
        }
        read_unlock(&current->fs->lock);
        current->total_link_count = 0;
        retval = link_path_walk(name, nd);
 out:
-       if (unlikely(current->audit_context
-                    && nd && nd->dentry && nd->dentry->d_inode))
+       if (likely(retval == 0)) {
+               if (unlikely(current->audit_context && nd && nd->dentry &&
+                               nd->dentry->d_inode))
                audit_inode(name, nd->dentry->d_inode, flags);
+       }
+       return retval;
+
+fput_unlock_fail:
+       fput_light(file, fput_needed);
+unlock_fail:
+       read_unlock(&current->fs->lock);
        return retval;
 }
 
-static int __path_lookup_intent_open(const char *name, unsigned int lookup_flags,
-               struct nameidata *nd, int open_flags, int create_mode)
+int fastcall path_lookup(const char *name, unsigned int flags,
+                       struct nameidata *nd)
+{
+       return do_path_lookup(AT_FDCWD, name, flags, nd);
+}
+
+static int __path_lookup_intent_open(int dfd, const char *name,
+               unsigned int lookup_flags, struct nameidata *nd,
+               int open_flags, int create_mode)
 {
        struct file *filp = get_empty_filp();
        int err;
@@ -1108,7 +1151,7 @@ static int __path_lookup_intent_open(const char *name, unsigned int lookup_flags
        nd->intent.open.file = filp;
        nd->intent.open.flags = open_flags;
        nd->intent.open.create_mode = create_mode;
-       err = path_lookup(name, lookup_flags|LOOKUP_OPEN, nd);
+       err = do_path_lookup(dfd, name, lookup_flags|LOOKUP_OPEN, nd);
        if (IS_ERR(nd->intent.open.file)) {
                if (err == 0) {
                        err = PTR_ERR(nd->intent.open.file);
@@ -1121,32 +1164,34 @@ static int __path_lookup_intent_open(const char *name, unsigned int lookup_flags
 
 /**
  * path_lookup_open - lookup a file path with open intent
+ * @dfd: the directory to use as base, or AT_FDCWD
  * @name: pointer to file name
  * @lookup_flags: lookup intent flags
  * @nd: pointer to nameidata
  * @open_flags: open intent flags
  */
-int path_lookup_open(const char *name, unsigned int lookup_flags,
+int path_lookup_open(int dfd, const char *name, unsigned int lookup_flags,
                struct nameidata *nd, int open_flags)
 {
-       return __path_lookup_intent_open(name, lookup_flags, nd,
+       return __path_lookup_intent_open(dfd, name, lookup_flags, nd,
                        open_flags, 0);
 }
 
 /**
  * path_lookup_create - lookup a file path with open + create intent
+ * @dfd: the directory to use as base, or AT_FDCWD
  * @name: pointer to file name
  * @lookup_flags: lookup intent flags
  * @nd: pointer to nameidata
  * @open_flags: open intent flags
  * @create_mode: create intent flags
  */
-static int path_lookup_create(const char *name, unsigned int lookup_flags,
-                             struct nameidata *nd, int open_flags,
-                             int create_mode)
+static int path_lookup_create(int dfd, const char *name,
+                             unsigned int lookup_flags, struct nameidata *nd,
+                             int open_flags, int create_mode)
 {
-       return __path_lookup_intent_open(name, lookup_flags|LOOKUP_CREATE, nd,
-                       open_flags, create_mode);
+       return __path_lookup_intent_open(dfd, name, lookup_flags|LOOKUP_CREATE,
+                       nd, open_flags, create_mode);
 }
 
 int __user_path_lookup_open(const char __user *name, unsigned int lookup_flags,
@@ -1156,7 +1201,7 @@ int __user_path_lookup_open(const char __user *name, unsigned int lookup_flags,
        int err = PTR_ERR(tmp);
 
        if (!IS_ERR(tmp)) {
-               err = __path_lookup_intent_open(tmp, lookup_flags, nd, open_flags, 0);
+               err = __path_lookup_intent_open(AT_FDCWD, tmp, lookup_flags, nd, open_flags, 0);
                putname(tmp);
        }
        return err;
@@ -1248,18 +1293,24 @@ access:
  * that namei follows links, while lnamei does not.
  * SMP-safe
  */
-int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd)
+int fastcall __user_walk_fd(int dfd, const char __user *name, unsigned flags,
+                           struct nameidata *nd)
 {
        char *tmp = getname(name);
        int err = PTR_ERR(tmp);
 
        if (!IS_ERR(tmp)) {
-               err = path_lookup(tmp, flags, nd);
+               err = do_path_lookup(dfd, tmp, flags, nd);
                putname(tmp);
        }
        return err;
 }
 
+int fastcall __user_walk(const char __user *name, unsigned flags, struct nameidata *nd)
+{
+       return __user_walk_fd(AT_FDCWD, name, flags, nd);
+}
+
 /*
  * It's inline, so penalty for filesystems that don't use sticky bit is
  * minimal.
@@ -1518,7 +1569,8 @@ int may_open(struct nameidata *nd, int acc_mode, int flag)
  * for symlinks (where the permissions are checked later).
  * SMP-safe
  */
-int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
+int open_namei(int dfd, const char *pathname, int flag,
+               int mode, struct nameidata *nd)
 {
        int acc_mode, error;
        struct path path;
@@ -1540,7 +1592,8 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
         * The simplest case - just a plain lookup.
         */
        if (!(flag & O_CREAT)) {
-               error = path_lookup_open(pathname, lookup_flags(flag), nd, flag);
+               error = path_lookup_open(dfd, pathname, lookup_flags(flag),
+                                        nd, flag);
                if (error)
                        return error;
                goto ok;
@@ -1549,7 +1602,7 @@ int open_namei(const char * pathname, int flag, int mode, struct nameidata *nd)
        /*
         * Create - we need to know the parent.
         */
-       error = path_lookup_create(pathname, LOOKUP_PARENT, nd, flag, mode);
+       error = path_lookup_create(dfd,pathname,LOOKUP_PARENT,nd,flag,mode);
        if (error)
                return error;
 
@@ -1744,7 +1797,8 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
        return error;
 }
 
-asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev)
+asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode,
+                               unsigned dev)
 {
        int error = 0;
        char * tmp;
@@ -1757,7 +1811,7 @@ asmlinkage long sys_mknod(const char __user * filename, int mode, unsigned dev)
        if (IS_ERR(tmp))
                return PTR_ERR(tmp);
 
-       error = path_lookup(tmp, LOOKUP_PARENT, &nd);
+       error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd);
        if (error)
                goto out;
        dentry = lookup_create(&nd, 0);
@@ -1793,6 +1847,11 @@ out:
        return error;
 }
 
+asmlinkage long sys_mknod(const char __user *filename, int mode, unsigned dev)
+{
+       return sys_mknodat(AT_FDCWD, filename, mode, dev);
+}
+
 int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
        int error = may_create(dir, dentry, NULL);
@@ -1815,7 +1874,7 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
        return error;
 }
 
-asmlinkage long sys_mkdir(const char __user * pathname, int mode)
+asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode)
 {
        int error = 0;
        char * tmp;
@@ -1826,7 +1885,7 @@ asmlinkage long sys_mkdir(const char __user * pathname, int mode)
                struct dentry *dentry;
                struct nameidata nd;
 
-               error = path_lookup(tmp, LOOKUP_PARENT, &nd);
+               error = do_path_lookup(dfd, tmp, LOOKUP_PARENT, &nd);
                if (error)
                        goto out;
                dentry = lookup_create(&nd, 1);
@@ -1846,6 +1905,11 @@ out:
        return error;
 }
 
+asmlinkage long sys_mkdir(const char __user *pathname, int mode)
+{
+       return sys_mkdirat(AT_FDCWD, pathname, mode);
+}
+
 /*
  * We try to drop the dentry early: we should have
  * a usage count of 2 if we're the only user of this
@@ -1907,7 +1971,7 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
        return error;
 }
 
-asmlinkage long sys_rmdir(const char __user * pathname)
+static long do_rmdir(int dfd, const char __user *pathname)
 {
        int error = 0;
        char * name;
@@ -1918,7 +1982,7 @@ asmlinkage long sys_rmdir(const char __user * pathname)
        if(IS_ERR(name))
                return PTR_ERR(name);
 
-       error = path_lookup(name, LOOKUP_PARENT, &nd);
+       error = do_path_lookup(dfd, name, LOOKUP_PARENT, &nd);
        if (error)
                goto exit;
 
@@ -1948,6 +2012,11 @@ exit:
        return error;
 }
 
+asmlinkage long sys_rmdir(const char __user *pathname)
+{
+       return do_rmdir(AT_FDCWD, pathname);
+}
+
 int vfs_unlink(struct inode *dir, struct dentry *dentry)
 {
        int error = may_delete(dir, dentry, 0);
@@ -1984,7 +2053,7 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
  * writeout happening, and we don't want to prevent access to the directory
  * while waiting on the I/O.
  */
-asmlinkage long sys_unlink(const char __user * pathname)
+static long do_unlinkat(int dfd, const char __user *pathname)
 {
        int error = 0;
        char * name;
@@ -1996,7 +2065,7 @@ asmlinkage long sys_unlink(const char __user * pathname)
        if(IS_ERR(name))
                return PTR_ERR(name);
 
-       error = path_lookup(name, LOOKUP_PARENT, &nd);
+       error = do_path_lookup(dfd, name, LOOKUP_PARENT, &nd);
        if (error)
                goto exit;
        error = -EISDIR;
@@ -2031,6 +2100,22 @@ slashes:
        goto exit2;
 }
 
+asmlinkage long sys_unlinkat(int dfd, const char __user *pathname, int flag)
+{
+       if ((flag & ~AT_REMOVEDIR) != 0)
+               return -EINVAL;
+
+       if (flag & AT_REMOVEDIR)
+               return do_rmdir(dfd, pathname);
+
+       return do_unlinkat(dfd, pathname);
+}
+
+asmlinkage long sys_unlink(const char __user *pathname)
+{
+       return do_unlinkat(AT_FDCWD, pathname);
+}
+
 int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, int mode)
 {
        int error = may_create(dir, dentry, NULL);
@@ -2052,7 +2137,8 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname, i
        return error;
 }
 
-asmlinkage long sys_symlink(const char __user * oldname, const char __user * newname)
+asmlinkage long sys_symlinkat(const char __user *oldname,
+                             int newdfd, const char __user *newname)
 {
        int error = 0;
        char * from;
@@ -2067,7 +2153,7 @@ asmlinkage long sys_symlink(const char __user * oldname, const char __user * new
                struct dentry *dentry;
                struct nameidata nd;
 
-               error = path_lookup(to, LOOKUP_PARENT, &nd);
+               error = do_path_lookup(newdfd, to, LOOKUP_PARENT, &nd);
                if (error)
                        goto out;
                dentry = lookup_create(&nd, 0);
@@ -2085,6 +2171,11 @@ out:
        return error;
 }
 
+asmlinkage long sys_symlink(const char __user *oldname, const char __user *newname)
+{
+       return sys_symlinkat(oldname, AT_FDCWD, newname);
+}
+
 int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
 {
        struct inode *inode = old_dentry->d_inode;
@@ -2132,7 +2223,8 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
  * with linux 2.0, and to avoid hard-linking to directories
  * and other special files.  --ADM
  */
-asmlinkage long sys_link(const char __user * oldname, const char __user * newname)
+asmlinkage long sys_linkat(int olddfd, const char __user *oldname,
+                          int newdfd, const char __user *newname)
 {
        struct dentry *new_dentry;
        struct nameidata nd, old_nd;
@@ -2143,10 +2235,10 @@ asmlinkage long sys_link(const char __user * oldname, const char __user * newnam
        if (IS_ERR(to))
                return PTR_ERR(to);
 
-       error = __user_walk(oldname, 0, &old_nd);
+       error = __user_walk_fd(olddfd, oldname, 0, &old_nd);
        if (error)
                goto exit;
-       error = path_lookup(to, LOOKUP_PARENT, &nd);
+       error = do_path_lookup(newdfd, to, LOOKUP_PARENT, &nd);
        if (error)
                goto out;
        error = -EXDEV;
@@ -2169,6 +2261,11 @@ exit:
        return error;
 }
 
+asmlinkage long sys_link(const char __user *oldname, const char __user *newname)
+{
+       return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname);
+}
+
 /*
  * The worst of all namespace operations - renaming directory. "Perverted"
  * doesn't even start to describe it. Somebody in UCB had a heck of a trip...
@@ -2315,7 +2412,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
        return error;
 }
 
-static int do_rename(const char * oldname, const char * newname)
+static int do_rename(int olddfd, const char *oldname,
+                       int newdfd, const char *newname)
 {
        int error = 0;
        struct dentry * old_dir, * new_dir;
@@ -2323,11 +2421,11 @@ static int do_rename(const char * oldname, const char * newname)
        struct dentry * trap;
        struct nameidata oldnd, newnd;
 
-       error = path_lookup(oldname, LOOKUP_PARENT, &oldnd);
+       error = do_path_lookup(olddfd, oldname, LOOKUP_PARENT, &oldnd);
        if (error)
                goto exit;
 
-       error = path_lookup(newname, LOOKUP_PARENT, &newnd);
+       error = do_path_lookup(newdfd, newname, LOOKUP_PARENT, &newnd);
        if (error)
                goto exit1;
 
@@ -2391,7 +2489,8 @@ exit:
        return error;
 }
 
-asmlinkage long sys_rename(const char __user * oldname, const char __user * newname)
+asmlinkage long sys_renameat(int olddfd, const char __user *oldname,
+                            int newdfd, const char __user *newname)
 {
        int error;
        char * from;
@@ -2403,13 +2502,18 @@ asmlinkage long sys_rename(const char __user * oldname, const char __user * newn
        to = getname(newname);
        error = PTR_ERR(to);
        if (!IS_ERR(to)) {
-               error = do_rename(from,to);
+               error = do_rename(olddfd, from, newdfd, to);
                putname(to);
        }
        putname(from);
        return error;
 }
 
+asmlinkage long sys_rename(const char __user *oldname, const char __user *newname)
+{
+       return sys_renameat(AT_FDCWD, oldname, AT_FDCWD, newname);
+}
+
 int vfs_readlink(struct dentry *dentry, char __user *buffer, int buflen, const char *link)
 {
        int len;
@@ -2553,6 +2657,7 @@ struct inode_operations page_symlink_inode_operations = {
 };
 
 EXPORT_SYMBOL(__user_walk);
+EXPORT_SYMBOL(__user_walk_fd);
 EXPORT_SYMBOL(follow_down);
 EXPORT_SYMBOL(follow_up);
 EXPORT_SYMBOL(get_write_access); /* binfmt_aout */