Cache root in nameidata
[safe/jmp/linux-2.6] / fs / namei.c
index 00c4f37..88baaf2 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/fsnotify.h>
 #include <linux/personality.h>
 #include <linux/security.h>
+#include <linux/ima.h>
 #include <linux/syscalls.h>
 #include <linux/mount.h>
 #include <linux/audit.h>
@@ -31,6 +32,7 @@
 #include <linux/file.h>
 #include <linux/fcntl.h>
 #include <linux/device_cgroup.h>
+#include <linux/fs_struct.h>
 #include <asm/uaccess.h>
 
 #define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
@@ -550,6 +552,17 @@ static __always_inline int link_path_walk(const char *name, struct nameidata *nd
        return result;
 }
 
+static __always_inline void set_root(struct nameidata *nd)
+{
+       if (!nd->root.mnt) {
+               struct fs_struct *fs = current->fs;
+               read_lock(&fs->lock);
+               nd->root = fs->root;
+               path_get(&nd->root);
+               read_unlock(&fs->lock);
+       }
+}
+
 static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
 {
        int res = 0;
@@ -558,14 +571,10 @@ static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *l
                goto fail;
 
        if (*link == '/') {
-               struct fs_struct *fs = current->fs;
-
+               set_root(nd);
                path_put(&nd->path);
-
-               read_lock(&fs->lock);
-               nd->path = fs->root;
-               path_get(&fs->root);
-               read_unlock(&fs->lock);
+               nd->path = nd->root;
+               path_get(&nd->root);
        }
 
        res = link_path_walk(link, nd);
@@ -739,19 +748,16 @@ int follow_down(struct vfsmount **mnt, struct dentry **dentry)
 
 static __always_inline void follow_dotdot(struct nameidata *nd)
 {
-       struct fs_struct *fs = current->fs;
+       set_root(nd);
 
        while(1) {
                struct vfsmount *parent;
                struct dentry *old = nd->path.dentry;
 
-                read_lock(&fs->lock);
-               if (nd->path.dentry == fs->root.dentry &&
-                   nd->path.mnt == fs->root.mnt) {
-                        read_unlock(&fs->lock);
+               if (nd->path.dentry == nd->root.dentry &&
+                   nd->path.mnt == nd->root.mnt) {
                        break;
                }
-                read_unlock(&fs->lock);
                spin_lock(&dcache_lock);
                if (nd->path.dentry != nd->path.mnt->mnt_root) {
                        nd->path.dentry = dget(nd->path.dentry->d_parent);
@@ -850,6 +856,9 @@ static int __link_path_walk(const char *name, struct nameidata *nd)
                if (err == -EAGAIN)
                        err = inode_permission(nd->path.dentry->d_inode,
                                               MAY_EXEC);
+               if (!err)
+                       err = ima_path_check(&nd->path, MAY_EXEC,
+                                            IMA_COUNT_UPDATE);
                if (err)
                        break;
 
@@ -1012,25 +1021,23 @@ static int path_walk(const char *name, struct nameidata *nd)
        return link_path_walk(name, nd);
 }
 
-/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int do_path_lookup(int dfd, const char *name,
-                               unsigned int flags, struct nameidata *nd)
+static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
 {
        int retval = 0;
        int fput_needed;
        struct file *file;
-       struct fs_struct *fs = current->fs;
 
        nd->last_type = LAST_ROOT; /* if there are only slashes... */
        nd->flags = flags;
        nd->depth = 0;
+       nd->root.mnt = NULL;
 
        if (*name=='/') {
-               read_lock(&fs->lock);
-               nd->path = fs->root;
-               path_get(&fs->root);
-               read_unlock(&fs->lock);
+               set_root(nd);
+               nd->path = nd->root;
+               path_get(&nd->root);
        } else if (dfd == AT_FDCWD) {
+               struct fs_struct *fs = current->fs;
                read_lock(&fs->lock);
                nd->path = fs->pwd;
                path_get(&fs->pwd);
@@ -1058,17 +1065,29 @@ static int do_path_lookup(int dfd, const char *name,
 
                fput_light(file, fput_needed);
        }
+       return 0;
 
-       retval = path_walk(name, nd);
+fput_fail:
+       fput_light(file, fput_needed);
+out_fail:
+       return retval;
+}
+
+/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
+static int do_path_lookup(int dfd, const char *name,
+                               unsigned int flags, struct nameidata *nd)
+{
+       int retval = path_init(dfd, name, flags, nd);
+       if (!retval)
+               retval = path_walk(name, nd);
        if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
                                nd->path.dentry->d_inode))
                audit_inode(name, nd->path.dentry);
-out_fail:
+       if (nd->root.mnt) {
+               path_put(&nd->root);
+               nd->root.mnt = NULL;
+       }
        return retval;
-
-fput_fail:
-       fput_light(file, fput_needed);
-       goto out_fail;
 }
 
 int path_lookup(const char *name, unsigned int flags,
@@ -1104,6 +1123,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
        nd->last_type = LAST_ROOT;
        nd->flags = flags;
        nd->depth = 0;
+       nd->root.mnt = NULL;
 
        nd->path.dentry = dentry;
        nd->path.mnt = mnt;
@@ -1114,8 +1134,12 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
                                nd->path.dentry->d_inode))
                audit_inode(name, nd->path.dentry);
 
-       return retval;
+       if (nd->root.mnt) {
+               path_put(&nd->root);
+               nd->root.mnt = NULL;
+       }
 
+       return retval;
 }
 
 /**
@@ -1126,8 +1150,8 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
  * @nd: pointer to nameidata
  * @open_flags: open intent flags
  */
-int path_lookup_open(int dfd, const char *name, unsigned int lookup_flags,
-               struct nameidata *nd, int open_flags)
+static int path_lookup_open(int dfd, const char *name,
+               unsigned int lookup_flags, struct nameidata *nd, int open_flags)
 {
        struct file *filp = get_empty_filp();
        int err;
@@ -1244,6 +1268,8 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
        int err;
        struct qstr this;
 
+       WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
+
        err = __lookup_one_len(name, &this, base, len);
        if (err)
                return ERR_PTR(err);
@@ -1470,7 +1496,7 @@ int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
        error = security_inode_create(dir, dentry, mode);
        if (error)
                return error;
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
        error = dir->i_op->create(dir, dentry, mode, nd);
        if (!error)
                fsnotify_create(dir, dentry);
@@ -1486,29 +1512,33 @@ int may_open(struct path *path, int acc_mode, int flag)
        if (!inode)
                return -ENOENT;
 
-       if (S_ISLNK(inode->i_mode))
+       switch (inode->i_mode & S_IFMT) {
+       case S_IFLNK:
                return -ELOOP;
-       
-       if (S_ISDIR(inode->i_mode) && (acc_mode & MAY_WRITE))
-               return -EISDIR;
-
-       /*
-        * FIFO's, sockets and device files are special: they don't
-        * actually live on the filesystem itself, and as such you
-        * can write to them even if the filesystem is read-only.
-        */
-       if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
-               flag &= ~O_TRUNC;
-       } else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {
+       case S_IFDIR:
+               if (acc_mode & MAY_WRITE)
+                       return -EISDIR;
+               break;
+       case S_IFBLK:
+       case S_IFCHR:
                if (path->mnt->mnt_flags & MNT_NODEV)
                        return -EACCES;
-
+               /*FALLTHRU*/
+       case S_IFIFO:
+       case S_IFSOCK:
                flag &= ~O_TRUNC;
+               break;
        }
 
        error = inode_permission(inode, acc_mode);
        if (error)
                return error;
+
+       error = ima_path_check(path,
+                              acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC),
+                              IMA_COUNT_UPDATE);
+       if (error)
+               return error;
        /*
         * An append-only file must be opened in append mode for writing.
         */
@@ -1544,7 +1574,7 @@ int may_open(struct path *path, int acc_mode, int flag)
                        error = security_path_truncate(path, 0,
                                               ATTR_MTIME|ATTR_CTIME|ATTR_OPEN);
                if (!error) {
-                       DQUOT_INIT(inode);
+                       vfs_dq_init(inode);
 
                        error = do_truncate(dentry, 0,
                                            ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
@@ -1555,7 +1585,7 @@ int may_open(struct path *path, int acc_mode, int flag)
                        return error;
        } else
                if (flag & FMODE_WRITE)
-                       DQUOT_INIT(inode);
+                       vfs_dq_init(inode);
 
        return 0;
 }
@@ -1572,7 +1602,7 @@ static int __open_namei_create(struct nameidata *nd, struct path *path,
        struct dentry *dir = nd->path.dentry;
 
        if (!IS_POSIXACL(dir->d_inode))
-               mode &= ~current->fs->umask;
+               mode &= ~current_umask();
        error = security_path_mknod(&nd->path, path->dentry, mode, 0);
        if (error)
                goto out_unlock;
@@ -1628,18 +1658,19 @@ static int open_will_write_to_fs(int flag, struct inode *inode)
  * open_to_namei_flags() for more details.
  */
 struct file *do_filp_open(int dfd, const char *pathname,
-               int open_flag, int mode)
+               int open_flag, int mode, int acc_mode)
 {
        struct file *filp;
        struct nameidata nd;
-       int acc_mode, error;
+       int error;
        struct path path;
        struct dentry *dir;
        int count = 0;
        int will_write;
        int flag = open_to_namei_flags(open_flag);
 
-       acc_mode = MAY_OPEN | ACC_MODE(flag);
+       if (!acc_mode)
+               acc_mode = MAY_OPEN | ACC_MODE(flag);
 
        /* O_TRUNC implies we need access checks for write permissions */
        if (flag & O_TRUNC)
@@ -1664,9 +1695,14 @@ struct file *do_filp_open(int dfd, const char *pathname,
        /*
         * Create - we need to know the parent.
         */
-       error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd);
+       error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);
+       if (error)
+               return ERR_PTR(error);
+       error = path_walk(pathname, &nd);
        if (error)
                return ERR_PTR(error);
+       if (unlikely(!audit_dummy_context()))
+               audit_inode(pathname, nd.path.dentry);
 
        /*
         * We have the parent and last component. First of all, check
@@ -1794,6 +1830,8 @@ exit:
        if (!IS_ERR(nd.intent.open.file))
                release_open_intent(&nd);
 exit_parent:
+       if (nd.root.mnt)
+               path_put(&nd.root);
        path_put(&nd.path);
        return ERR_PTR(error);
 
@@ -1860,7 +1898,7 @@ do_link:
  */
 struct file *filp_open(const char *filename, int flags, int mode)
 {
-       return do_filp_open(AT_FDCWD, filename, flags, mode);
+       return do_filp_open(AT_FDCWD, filename, flags, mode, 0);
 }
 EXPORT_SYMBOL(filp_open);
 
@@ -1938,7 +1976,7 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
        if (error)
                return error;
 
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
        error = dir->i_op->mknod(dir, dentry, mode, dev);
        if (!error)
                fsnotify_create(dir, dentry);
@@ -1962,8 +2000,8 @@ static int may_mknod(mode_t mode)
        }
 }
 
-asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode,
-                               unsigned dev)
+SYSCALL_DEFINE4(mknodat, int, dfd, const char __user *, filename, int, mode,
+               unsigned, dev)
 {
        int error;
        char *tmp;
@@ -1983,7 +2021,7 @@ asmlinkage long sys_mknodat(int dfd, const char __user *filename, int mode,
                goto out_unlock;
        }
        if (!IS_POSIXACL(nd.path.dentry->d_inode))
-               mode &= ~current->fs->umask;
+               mode &= ~current_umask();
        error = may_mknod(mode);
        if (error)
                goto out_dput;
@@ -2037,14 +2075,14 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
        if (error)
                return error;
 
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
        error = dir->i_op->mkdir(dir, dentry, mode);
        if (!error)
                fsnotify_mkdir(dir, dentry);
        return error;
 }
 
-asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode)
+SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, int, mode)
 {
        int error = 0;
        char * tmp;
@@ -2061,7 +2099,7 @@ asmlinkage long sys_mkdirat(int dfd, const char __user *pathname, int mode)
                goto out_unlock;
 
        if (!IS_POSIXACL(nd.path.dentry->d_inode))
-               mode &= ~current->fs->umask;
+               mode &= ~current_umask();
        error = mnt_want_write(nd.path.mnt);
        if (error)
                goto out_dput;
@@ -2081,7 +2119,7 @@ out_err:
        return error;
 }
 
-asmlinkage long sys_mkdir(const char __user *pathname, int mode)
+SYSCALL_DEFINE2(mkdir, const char __user *, pathname, int, mode)
 {
        return sys_mkdirat(AT_FDCWD, pathname, mode);
 }
@@ -2123,7 +2161,7 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
        if (!dir->i_op->rmdir)
                return -EPERM;
 
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
 
        mutex_lock(&dentry->d_inode->i_mutex);
        dentry_unhash(dentry);
@@ -2195,7 +2233,7 @@ exit1:
        return error;
 }
 
-asmlinkage long sys_rmdir(const char __user *pathname)
+SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
 {
        return do_rmdir(AT_FDCWD, pathname);
 }
@@ -2210,7 +2248,7 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
        if (!dir->i_op->unlink)
                return -EPERM;
 
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
 
        mutex_lock(&dentry->d_inode->i_mutex);
        if (d_mountpoint(dentry))
@@ -2291,7 +2329,7 @@ slashes:
        goto exit2;
 }
 
-asmlinkage long sys_unlinkat(int dfd, const char __user *pathname, int flag)
+SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag)
 {
        if ((flag & ~AT_REMOVEDIR) != 0)
                return -EINVAL;
@@ -2321,15 +2359,15 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
        if (error)
                return error;
 
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
        error = dir->i_op->symlink(dir, dentry, oldname);
        if (!error)
                fsnotify_create(dir, dentry);
        return error;
 }
 
-asmlinkage long sys_symlinkat(const char __user *oldname,
-                             int newdfd, const char __user *newname)
+SYSCALL_DEFINE3(symlinkat, const char __user *, oldname,
+               int, newdfd, const char __user *, newname)
 {
        int error;
        char *from;
@@ -2405,7 +2443,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
                return error;
 
        mutex_lock(&inode->i_mutex);
-       DQUOT_INIT(dir);
+       vfs_dq_init(dir);
        error = dir->i_op->link(old_dentry, dir, new_dentry);
        mutex_unlock(&inode->i_mutex);
        if (!error)
@@ -2422,9 +2460,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_linkat(int olddfd, const char __user *oldname,
-                          int newdfd, const char __user *newname,
-                          int flags)
+SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
+               int, newdfd, const char __user *, newname, int, flags)
 {
        struct dentry *new_dentry;
        struct nameidata nd;
@@ -2605,8 +2642,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
        if (!old_dir->i_op->rename)
                return -EPERM;
 
-       DQUOT_INIT(old_dir);
-       DQUOT_INIT(new_dir);
+       vfs_dq_init(old_dir);
+       vfs_dq_init(new_dir);
 
        old_name = fsnotify_oldname_init(old_dentry->d_name.name);
 
@@ -2624,8 +2661,8 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
        return error;
 }
 
-asmlinkage long sys_renameat(int olddfd, const char __user *oldname,
-                            int newdfd, const char __user *newname)
+SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
+               int, newdfd, const char __user *, newname)
 {
        struct dentry *old_dir, *new_dir;
        struct dentry *old_dentry, *new_dentry;
@@ -2892,10 +2929,3 @@ EXPORT_SYMBOL(vfs_symlink);
 EXPORT_SYMBOL(vfs_unlink);
 EXPORT_SYMBOL(dentry_unhash);
 EXPORT_SYMBOL(generic_readlink);
-
-/* to be mentioned only in INIT_TASK */
-struct fs_struct init_fs = {
-       .count          = ATOMIC_INIT(1),
-       .lock           = __RW_LOCK_UNLOCKED(init_fs.lock),
-       .umask          = 0022,
-};