net: ipv6: Fix ping to link-local addresses.
[GitHub/LineageOS/android_kernel_samsung_universal7580.git] / fs / namei.c
index 9ed9361223c08f30ebbcca48e165588adeb31378..b2a525fe95d10018ccbc3ca681f38b32ccf553c1 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/device_cgroup.h>
 #include <linux/fs_struct.h>
 #include <linux/posix_acl.h>
+#include <linux/hash.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -321,10 +322,11 @@ int generic_permission(struct inode *inode, int mask)
 
        if (S_ISDIR(inode->i_mode)) {
                /* DACs are overridable for directories */
-               if (inode_capable(inode, CAP_DAC_OVERRIDE))
+               if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
                        return 0;
                if (!(mask & MAY_WRITE))
-                       if (inode_capable(inode, CAP_DAC_READ_SEARCH))
+                       if (capable_wrt_inode_uidgid(inode,
+                                                    CAP_DAC_READ_SEARCH))
                                return 0;
                return -EACCES;
        }
@@ -334,7 +336,7 @@ int generic_permission(struct inode *inode, int mask)
         * at least one exec bit set.
         */
        if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO))
-               if (inode_capable(inode, CAP_DAC_OVERRIDE))
+               if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE))
                        return 0;
 
        /*
@@ -342,7 +344,7 @@ int generic_permission(struct inode *inode, int mask)
         */
        mask &= MAY_READ | MAY_WRITE | MAY_EXEC;
        if (mask == MAY_READ)
-               if (inode_capable(inode, CAP_DAC_READ_SEARCH))
+               if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH))
                        return 0;
 
        return -EACCES;
@@ -354,9 +356,11 @@ int generic_permission(struct inode *inode, int mask)
  * flag in inode->i_opflags, that says "this has not special
  * permission function, use the fast case".
  */
-static inline int do_inode_permission(struct inode *inode, int mask)
+static inline int do_inode_permission(struct vfsmount *mnt, struct inode *inode, int mask)
 {
        if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) {
+               if (likely(mnt && inode->i_op->permission2))
+                       return inode->i_op->permission2(mnt, inode, mask);
                if (likely(inode->i_op->permission))
                        return inode->i_op->permission(inode, mask);
 
@@ -380,7 +384,7 @@ static inline int do_inode_permission(struct inode *inode, int mask)
  * This does not check for a read-only file system.  You probably want
  * inode_permission().
  */
-int __inode_permission(struct inode *inode, int mask)
+int __inode_permission2(struct vfsmount *mnt, struct inode *inode, int mask)
 {
        int retval;
 
@@ -392,7 +396,7 @@ int __inode_permission(struct inode *inode, int mask)
                        return -EACCES;
        }
 
-       retval = do_inode_permission(inode, mask);
+       retval = do_inode_permission(mnt, inode, mask);
        if (retval)
                return retval;
 
@@ -402,6 +406,13 @@ int __inode_permission(struct inode *inode, int mask)
 
        return security_inode_permission(inode, mask);
 }
+EXPORT_SYMBOL(__inode_permission2);
+
+int __inode_permission(struct inode *inode, int mask)
+{
+       return __inode_permission2(NULL, inode, mask);
+}
+EXPORT_SYMBOL(__inode_permission);
 
 /**
  * sb_permission - Check superblock-level permissions
@@ -435,15 +446,22 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask)
  *
  * When checking for MAY_APPEND, MAY_WRITE must also be set in @mask.
  */
-int inode_permission(struct inode *inode, int mask)
+int inode_permission2(struct vfsmount *mnt, struct inode *inode, int mask)
 {
        int retval;
 
        retval = sb_permission(inode->i_sb, inode, mask);
        if (retval)
                return retval;
-       return __inode_permission(inode, mask);
+       return __inode_permission2(mnt, inode, mask);
+}
+EXPORT_SYMBOL(inode_permission2);
+
+int inode_permission(struct inode *inode, int mask)
+{
+       return inode_permission2(NULL, inode, mask);
 }
+EXPORT_SYMBOL(inode_permission);
 
 /**
  * path_get - get a reference to a path
@@ -471,6 +489,24 @@ void path_put(const struct path *path)
 }
 EXPORT_SYMBOL(path_put);
 
+/**
+ * path_connected - Verify that a path->dentry is below path->mnt.mnt_root
+ * @path: nameidate to verify
+ *
+ * Rename can sometimes move a file or directory outside of a bind
+ * mount, path_connected allows those cases to be detected.
+ */
+static bool path_connected(const struct path *path)
+{
+       struct vfsmount *mnt = path->mnt;
+
+       /* Only bind mounts can have disconnected paths */
+       if (mnt->mnt_root == mnt->mnt_sb->s_root)
+               return true;
+
+       return is_subdir(path->dentry, mnt->mnt_root);
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -1146,6 +1182,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
                                goto failed;
                        nd->path.dentry = parent;
                        nd->seq = seq;
+                       if (unlikely(!path_connected(&nd->path)))
+                               goto failed;
                        break;
                }
                if (!follow_up_rcu(&nd->path))
@@ -1229,7 +1267,7 @@ static void follow_mount(struct path *path)
        }
 }
 
-static void follow_dotdot(struct nameidata *nd)
+static int follow_dotdot(struct nameidata *nd)
 {
        set_root(nd);
 
@@ -1244,6 +1282,10 @@ static void follow_dotdot(struct nameidata *nd)
                        /* rare case of legitimate dget_parent()... */
                        nd->path.dentry = dget_parent(nd->path.dentry);
                        dput(old);
+                       if (unlikely(!path_connected(&nd->path))) {
+                               path_put(&nd->path);
+                               return -ENOENT;
+                       }
                        break;
                }
                if (!follow_up(&nd->path))
@@ -1251,6 +1293,7 @@ static void follow_dotdot(struct nameidata *nd)
        }
        follow_mount(&nd->path);
        nd->inode = nd->path.dentry->d_inode;
+       return 0;
 }
 
 /*
@@ -1458,13 +1501,13 @@ static int lookup_slow(struct nameidata *nd, struct path *path)
 static inline int may_lookup(struct nameidata *nd)
 {
        if (nd->flags & LOOKUP_RCU) {
-               int err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
+               int err = inode_permission2(nd->path.mnt, nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
                if (err != -ECHILD)
                        return err;
                if (unlazy_walk(nd, NULL))
                        return -ECHILD;
        }
-       return inode_permission(nd->inode, MAY_EXEC);
+       return inode_permission2(nd->path.mnt, nd->inode, MAY_EXEC);
 }
 
 static inline int handle_dots(struct nameidata *nd, int type)
@@ -1474,7 +1517,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
                        if (follow_dotdot_rcu(nd))
                                return -ECHILD;
                } else
-                       follow_dotdot(nd);
+                       return follow_dotdot(nd);
        }
        return 0;
 }
@@ -1540,7 +1583,8 @@ static inline int walk_component(struct nameidata *nd, struct path *path,
 
        if (should_follow_link(inode, follow)) {
                if (nd->flags & LOOKUP_RCU) {
-                       if (unlikely(unlazy_walk(nd, path->dentry))) {
+                       if (unlikely(nd->path.mnt != path->mnt ||
+                                    unlazy_walk(nd, path->dentry))) {
                                err = -ECHILD;
                                goto out_err;
                        }
@@ -1646,8 +1690,7 @@ static inline int can_lookup(struct inode *inode)
 
 static inline unsigned int fold_hash(unsigned long hash)
 {
-       hash += hash >> (8*sizeof(int));
-       return hash;
+       return hash_64(hash, 32);
 }
 
 #else  /* 32-bit case */
@@ -1839,10 +1882,11 @@ static int path_init(int dfd, const char *name, unsigned int flags,
        nd->depth = 0;
        if (flags & LOOKUP_ROOT) {
                struct inode *inode = nd->root.dentry->d_inode;
+               struct vfsmount *mnt = nd->root.mnt;
                if (*name) {
                        if (!can_lookup(inode))
                                return -ENOTDIR;
-                       retval = inode_permission(inode, MAY_EXEC);
+                       retval = inode_permission2(mnt, inode, MAY_EXEC);
                        if (retval)
                                return retval;
                }
@@ -1982,6 +2026,16 @@ static int path_lookupat(int dfd, const char *name,
                }
        }
 
+       if (!err) {
+               struct super_block *sb = nd->inode->i_sb;
+               if (sb->s_flags & MS_RDONLY) {
+                       if (d_is_su(nd->path.dentry) && !su_visible()) {
+                               path_put(&nd->path);
+                               err = -ENOENT;
+                       }
+               }
+       }
+
        if (base)
                fput(base);
 
@@ -2084,6 +2138,7 @@ static struct dentry *lookup_hash(struct nameidata *nd)
 /**
  * lookup_one_len - filesystem helper to lookup single pathname component
  * @name:      pathname component to lookup
+ * @mnt:       mount we are looking up on
  * @base:      base directory to lookup from
  * @len:       maximum length @len should be interpreted to
  *
@@ -2092,7 +2147,7 @@ static struct dentry *lookup_hash(struct nameidata *nd)
  * nameidata argument is passed to the filesystem methods and a filesystem
  * using this helper needs to be prepared for that.
  */
-struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
+struct dentry *lookup_one_len2(const char *name, struct vfsmount *mnt, struct dentry *base, int len)
 {
        struct qstr this;
        unsigned int c;
@@ -2126,12 +2181,19 @@ struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
                        return ERR_PTR(err);
        }
 
-       err = inode_permission(base->d_inode, MAY_EXEC);
+       err = inode_permission2(mnt, base->d_inode, MAY_EXEC);
        if (err)
                return ERR_PTR(err);
 
        return __lookup_hash(&this, base, 0);
 }
+EXPORT_SYMBOL(lookup_one_len2);
+
+struct dentry *lookup_one_len(const char *name, struct dentry *base, int len)
+{
+       return lookup_one_len2(name, NULL, base, len);
+}
+EXPORT_SYMBOL(lookup_one_len);
 
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
                 struct path *path, int *empty)
@@ -2199,7 +2261,7 @@ static inline int check_sticky(struct inode *dir, struct inode *inode)
                return 0;
        if (uid_eq(dir->i_uid, fsuid))
                return 0;
-       return !inode_capable(inode, CAP_FOWNER);
+       return !capable_wrt_inode_uidgid(inode, CAP_FOWNER);
 }
 
 /*
@@ -2221,7 +2283,7 @@ static inline int check_sticky(struct inode *dir, struct inode *inode)
  * 10. We don't allow removal of NFS sillyrenamed files; it's handled by
  *     nfs_async_unlink().
  */
-static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
+static int may_delete(struct vfsmount *mnt, struct inode *dir,struct dentry *victim,int isdir)
 {
        int error;
 
@@ -2231,7 +2293,7 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
        BUG_ON(victim->d_parent->d_inode != dir);
        audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE);
 
-       error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
+       error = inode_permission2(mnt, dir, MAY_WRITE | MAY_EXEC);
        if (error)
                return error;
        if (IS_APPEND(dir))
@@ -2261,13 +2323,14 @@ static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
  *  3. We should have write and exec permissions on dir
  *  4. We can't do it if dir is immutable (done in permission())
  */
-static inline int may_create(struct inode *dir, struct dentry *child)
+static inline int may_create(struct vfsmount *mnt, struct inode *dir, struct dentry *child)
 {
+       audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE);
        if (child->d_inode)
                return -EEXIST;
        if (IS_DEADDIR(dir))
                return -ENOENT;
-       return inode_permission(dir, MAY_WRITE | MAY_EXEC);
+       return inode_permission2(mnt, dir, MAY_WRITE | MAY_EXEC);
 }
 
 /*
@@ -2312,10 +2375,10 @@ void unlock_rename(struct dentry *p1, struct dentry *p2)
        }
 }
 
-int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
-               bool want_excl)
+int vfs_create2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry,
+               umode_t mode, bool want_excl)
 {
-       int error = may_create(dir, dentry);
+       int error = may_create(mnt, dir, dentry);
        if (error)
                return error;
 
@@ -2331,10 +2394,19 @@ int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
                fsnotify_create(dir, dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_create2);
+
+int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
+               bool want_excl)
+{
+       return vfs_create2(NULL, dir, dentry, mode, want_excl);
+}
+EXPORT_SYMBOL(vfs_create);
 
 static int may_open(struct path *path, int acc_mode, int flag)
 {
        struct dentry *dentry = path->dentry;
+       struct vfsmount *mnt = path->mnt;
        struct inode *inode = dentry->d_inode;
        int error;
 
@@ -2363,7 +2435,7 @@ static int may_open(struct path *path, int acc_mode, int flag)
                break;
        }
 
-       error = inode_permission(inode, acc_mode);
+       error = inode_permission2(mnt, inode, acc_mode);
        if (error)
                return error;
 
@@ -2398,7 +2470,7 @@ static int handle_truncate(struct file *filp)
        if (!error)
                error = security_path_truncate(path);
        if (!error) {
-               error = do_truncate(path->dentry, 0,
+               error = do_truncate2(path->mnt, path->dentry, 0,
                                    ATTR_MTIME|ATTR_CTIME|ATTR_OPEN,
                                    filp);
        }
@@ -2419,7 +2491,7 @@ static int may_o_create(struct path *dir, struct dentry *dentry, umode_t mode)
        if (error)
                return error;
 
-       error = inode_permission(dir->dentry->d_inode, MAY_WRITE | MAY_EXEC);
+       error = inode_permission2(dir->mnt, dir->dentry->d_inode, MAY_WRITE | MAY_EXEC);
        if (error)
                return error;
 
@@ -2607,6 +2679,7 @@ static int lookup_open(struct nameidata *nd, struct path *path,
                        bool got_write, int *opened)
 {
        struct dentry *dir = nd->path.dentry;
+       struct vfsmount *mnt = nd->path.mnt;
        struct inode *dir_inode = dir->d_inode;
        struct dentry *dentry;
        int error;
@@ -2654,7 +2727,7 @@ static int lookup_open(struct nameidata *nd, struct path *path,
                error = security_path_mknod(&nd->path, dentry, mode, 0);
                if (error)
                        goto out_dput;
-               error = vfs_create(dir->d_inode, dentry, mode,
+               error = vfs_create2(mnt, dir->d_inode, dentry, mode,
                                   nd->flags & LOOKUP_EXCL);
                if (error)
                        goto out_dput;
@@ -2690,28 +2763,10 @@ static int do_last(struct nameidata *nd, struct path *path,
        nd->flags &= ~LOOKUP_PARENT;
        nd->flags |= op->intent;
 
-       switch (nd->last_type) {
-       case LAST_DOTDOT:
-       case LAST_DOT:
+       if (nd->last_type != LAST_NORM) {
                error = handle_dots(nd, nd->last_type);
                if (error)
                        return error;
-               /* fallthrough */
-       case LAST_ROOT:
-               error = complete_walk(nd);
-               if (error)
-                       return error;
-               audit_inode(name, nd->path.dentry, 0);
-               if (open_flag & O_CREAT) {
-                       error = -EISDIR;
-                       goto out;
-               }
-               goto finish_open;
-       case LAST_BIND:
-               error = complete_walk(nd);
-               if (error)
-                       return error;
-               audit_inode(name, dir, 0);
                goto finish_open;
        }
 
@@ -2822,7 +2877,8 @@ finish_lookup:
 
        if (should_follow_link(inode, !symlink_ok)) {
                if (nd->flags & LOOKUP_RCU) {
-                       if (unlikely(unlazy_walk(nd, path->dentry))) {
+                       if (unlikely(nd->path.mnt != path->mnt ||
+                                    unlazy_walk(nd, path->dentry))) {
                                error = -ECHILD;
                                goto out;
                        }
@@ -2841,19 +2897,19 @@ finish_lookup:
        }
        nd->inode = inode;
        /* Why this, you ask?  _Now_ we might have grown LOOKUP_JUMPED... */
+finish_open:
        error = complete_walk(nd);
        if (error) {
                path_put(&save_parent);
                return error;
        }
+       audit_inode(name, nd->path.dentry, 0);
        error = -EISDIR;
        if ((open_flag & O_CREAT) && S_ISDIR(nd->inode->i_mode))
                goto out;
        error = -ENOTDIR;
        if ((nd->flags & LOOKUP_DIRECTORY) && !can_lookup(nd->inode))
                goto out;
-       audit_inode(name, nd->path.dentry, 0);
-finish_open:
        if (!S_ISREG(nd->inode->i_mode))
                will_truncate = false;
 
@@ -2888,6 +2944,10 @@ opened:
                        goto exit_fput;
        }
 out:
+       if (unlikely(error > 0)) {
+               WARN_ON(1);
+               error = -EINVAL;
+       }
        if (got_write)
                mnt_drop_write(nd->path.mnt);
        path_put(&save_parent);
@@ -2920,6 +2980,62 @@ stale_open:
        goto retry_lookup;
 }
 
+static int do_tmpfile(int dfd, struct filename *pathname,
+               struct nameidata *nd, int flags,
+               const struct open_flags *op,
+               struct file *file, int *opened)
+{
+       static const struct qstr name = QSTR_INIT("/", 1);
+       struct dentry *dentry, *child;
+       struct inode *dir;
+       int error = path_lookupat(dfd, pathname->name,
+                                 flags | LOOKUP_DIRECTORY, nd);
+       if (unlikely(error))
+               return error;
+       error = mnt_want_write(nd->path.mnt);
+       if (unlikely(error))
+               goto out;
+       /* we want directory to be writable */
+       error = inode_permission(nd->inode, MAY_WRITE | MAY_EXEC);
+       if (error)
+               goto out2;
+       dentry = nd->path.dentry;
+       dir = dentry->d_inode;
+       if (!dir->i_op->tmpfile) {
+               error = -EOPNOTSUPP;
+               goto out2;
+       }
+       child = d_alloc(dentry, &name);
+       if (unlikely(!child)) {
+               error = -ENOMEM;
+               goto out2;
+       }
+       nd->flags &= ~LOOKUP_DIRECTORY;
+       nd->flags |= op->intent;
+       dput(nd->path.dentry);
+       nd->path.dentry = child;
+       error = dir->i_op->tmpfile(dir, nd->path.dentry, op->mode);
+       if (error)
+               goto out2;
+       audit_inode(pathname, nd->path.dentry, 0);
+       /* Don't check for other permissions, the inode was just created */
+       error = may_open(&nd->path, MAY_OPEN, op->open_flag);
+       if (error)
+               goto out2;
+       file->f_path.mnt = nd->path.mnt;
+       error = finish_open(file, nd->path.dentry, NULL, opened);
+       if (error)
+               goto out2;
+       error = open_check_o_direct(file);
+       if (error)
+               fput(file);
+out2:
+       mnt_drop_write(nd->path.mnt);
+out:
+       path_put(&nd->path);
+       return error;
+}
+
 static struct file *path_openat(int dfd, struct filename *pathname,
                struct nameidata *nd, const struct open_flags *op, int flags)
 {
@@ -2935,6 +3051,11 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 
        file->f_flags = op->open_flag;
 
+       if (unlikely(file->f_flags & __O_TMPFILE)) {
+               error = do_tmpfile(dfd, pathname, nd, flags, op, file, &opened);
+               goto out2;
+       }
+
        error = path_init(dfd, pathname->name, flags | LOOKUP_PARENT, nd, &base);
        if (unlikely(error))
                goto out;
@@ -2970,6 +3091,7 @@ out:
                path_put(&nd->root);
        if (base)
                fput(base);
+out2:
        if (!(opened & FILE_OPENED)) {
                BUG_ON(!error);
                put_filp(file);
@@ -2987,9 +3109,10 @@ out:
 }
 
 struct file *do_filp_open(int dfd, struct filename *pathname,
-               const struct open_flags *op, int flags)
+               const struct open_flags *op)
 {
        struct nameidata nd;
+       int flags = op->lookup_flags;
        struct file *filp;
 
        filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
@@ -3001,17 +3124,16 @@ struct file *do_filp_open(int dfd, struct filename *pathname,
 }
 
 struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
-               const char *name, const struct open_flags *op, int flags)
+               const char *name, const struct open_flags *op)
 {
        struct nameidata nd;
        struct file *file;
        struct filename filename = { .name = name };
+       int flags = op->lookup_flags | LOOKUP_ROOT;
 
        nd.root.mnt = mnt;
        nd.root.dentry = dentry;
 
-       flags |= LOOKUP_ROOT;
-
        if (dentry->d_inode->i_op->follow_link && op->intent & LOOKUP_OPEN)
                return ERR_PTR(-ELOOP);
 
@@ -3115,9 +3237,9 @@ struct dentry *user_path_create(int dfd, const char __user *pathname,
 }
 EXPORT_SYMBOL(user_path_create);
 
-int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
+int vfs_mknod2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
 {
-       int error = may_create(dir, dentry);
+       int error = may_create(mnt, dir, dentry);
 
        if (error)
                return error;
@@ -3141,6 +3263,13 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
                fsnotify_create(dir, dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_mknod2);
+
+int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
+{
+       return vfs_mknod2(NULL, dir, dentry, mode, dev);
+}
+EXPORT_SYMBOL(vfs_mknod);
 
 static int may_mknod(umode_t mode)
 {
@@ -3182,10 +3311,10 @@ retry:
                goto out;
        switch (mode & S_IFMT) {
                case 0: case S_IFREG:
-                       error = vfs_create(path.dentry->d_inode,dentry,mode,true);
+                       error = vfs_create2(path.mnt, path.dentry->d_inode,dentry,mode,true);
                        break;
                case S_IFCHR: case S_IFBLK:
-                       error = vfs_mknod(path.dentry->d_inode,dentry,mode,
+                       error = vfs_mknod2(path.mnt, path.dentry->d_inode,dentry,mode,
                                        new_decode_dev(dev));
                        break;
                case S_IFIFO: case S_IFSOCK:
@@ -3206,9 +3335,9 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
        return sys_mknodat(AT_FDCWD, filename, mode, dev);
 }
 
-int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+int vfs_mkdir2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry, umode_t mode)
 {
-       int error = may_create(dir, dentry);
+       int error = may_create(mnt, dir, dentry);
        unsigned max_links = dir->i_sb->s_max_links;
 
        if (error)
@@ -3230,6 +3359,13 @@ int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
                fsnotify_mkdir(dir, dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_mkdir2);
+
+int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+{
+       return vfs_mkdir2(NULL, dir, dentry, mode);
+}
+EXPORT_SYMBOL(vfs_mkdir);
 
 SYSCALL_DEFINE3(mkdirat, int, dfd, const char __user *, pathname, umode_t, mode)
 {
@@ -3247,7 +3383,7 @@ retry:
                mode &= ~current_umask();
        error = security_path_mkdir(&path, dentry, mode);
        if (!error)
-               error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
+               error = vfs_mkdir2(path.mnt, path.dentry->d_inode, dentry, mode);
        done_path_create(&path, dentry);
        if (retry_estale(error, lookup_flags)) {
                lookup_flags |= LOOKUP_REVAL;
@@ -3285,9 +3421,9 @@ void dentry_unhash(struct dentry *dentry)
        spin_unlock(&dentry->d_lock);
 }
 
-int vfs_rmdir(struct inode *dir, struct dentry *dentry)
+int vfs_rmdir2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry)
 {
-       int error = may_delete(dir, dentry, 1);
+       int error = may_delete(mnt, dir, dentry, 1);
 
        if (error)
                return error;
@@ -3321,6 +3457,13 @@ out:
                d_delete(dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_rmdir2);
+
+int vfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+       return vfs_rmdir2(NULL, dir, dentry);
+}
+EXPORT_SYMBOL(vfs_rmdir);
 
 static long do_rmdir(int dfd, const char __user *pathname)
 {
@@ -3329,6 +3472,8 @@ static long do_rmdir(int dfd, const char __user *pathname)
        struct dentry *dentry;
        struct nameidata nd;
        unsigned int lookup_flags = 0;
+       char *path_buf = NULL;
+       char *propagate_path = NULL;
 retry:
        name = user_path_parent(dfd, pathname, &nd, lookup_flags);
        if (IS_ERR(name))
@@ -3363,11 +3508,19 @@ retry:
        error = security_path_rmdir(&nd.path, dentry);
        if (error)
                goto exit3;
-       error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+       error = vfs_rmdir2(nd.path.mnt, nd.path.dentry->d_inode, dentry);
 exit3:
        dput(dentry);
 exit2:
        mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+       if (path_buf && !error) {
+               nd.path.dentry->d_sb->s_op->unlink_callback(nd.path.dentry->d_sb,
+                       propagate_path);
+       }
+       if (path_buf) {
+               kfree(path_buf);
+               path_buf = NULL;
+       }
        mnt_drop_write(nd.path.mnt);
 exit1:
        path_put(&nd.path);
@@ -3384,9 +3537,9 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
        return do_rmdir(AT_FDCWD, pathname);
 }
 
-int vfs_unlink(struct inode *dir, struct dentry *dentry)
+int vfs_unlink2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry)
 {
-       int error = may_delete(dir, dentry, 0);
+       int error = may_delete(mnt, dir, dentry, 0);
 
        if (error)
                return error;
@@ -3415,6 +3568,13 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
 
        return error;
 }
+EXPORT_SYMBOL(vfs_unlink2);
+
+int vfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+       return vfs_unlink2(NULL, dir, dentry);
+}
+EXPORT_SYMBOL(vfs_unlink);
 
 /*
  * Make sure that the actual truncation of the file will occur outside its
@@ -3430,6 +3590,8 @@ static long do_unlinkat(int dfd, const char __user *pathname)
        struct nameidata nd;
        struct inode *inode = NULL;
        unsigned int lookup_flags = 0;
+       char *path_buf = NULL;
+       char *propagate_path = NULL;
 retry:
        name = user_path_parent(dfd, pathname, &nd, lookup_flags);
        if (IS_ERR(name))
@@ -3454,15 +3616,26 @@ retry:
                inode = dentry->d_inode;
                if (!inode)
                        goto slashes;
+               if (inode->i_sb->s_op->unlink_callback) {
+                       path_buf = kmalloc(PATH_MAX, GFP_KERNEL);
+                       propagate_path = dentry_path_raw(dentry, path_buf, PATH_MAX);
+               }
                ihold(inode);
                error = security_path_unlink(&nd.path, dentry);
                if (error)
                        goto exit2;
-               error = vfs_unlink(nd.path.dentry->d_inode, dentry);
+               error = vfs_unlink2(nd.path.mnt, nd.path.dentry->d_inode, dentry);
 exit2:
                dput(dentry);
        }
        mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+       if (path_buf && !error) {
+               inode->i_sb->s_op->unlink_callback(inode->i_sb, propagate_path);
+       }
+       if (path_buf) {
+               kfree(path_buf);
+               path_buf = NULL;
+       }
        if (inode)
                iput(inode);    /* truncate the inode here */
        mnt_drop_write(nd.path.mnt);
@@ -3498,9 +3671,9 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname)
        return do_unlinkat(AT_FDCWD, pathname);
 }
 
-int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
+int vfs_symlink2(struct vfsmount *mnt, struct inode *dir, struct dentry *dentry, const char *oldname)
 {
-       int error = may_create(dir, dentry);
+       int error = may_create(mnt, dir, dentry);
 
        if (error)
                return error;
@@ -3517,6 +3690,13 @@ int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
                fsnotify_create(dir, dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_symlink2);
+
+int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
+{
+       return vfs_symlink2(NULL, dir, dentry, oldname);
+}
+EXPORT_SYMBOL(vfs_symlink);
 
 SYSCALL_DEFINE3(symlinkat, const char __user *, oldname,
                int, newdfd, const char __user *, newname)
@@ -3538,7 +3718,7 @@ retry:
 
        error = security_path_symlink(&path, dentry, from->name);
        if (!error)
-               error = vfs_symlink(path.dentry->d_inode, dentry, from->name);
+               error = vfs_symlink2(path.mnt, path.dentry->d_inode, dentry, from->name);
        done_path_create(&path, dentry);
        if (retry_estale(error, lookup_flags)) {
                lookup_flags |= LOOKUP_REVAL;
@@ -3554,7 +3734,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
        return sys_symlinkat(oldname, AT_FDCWD, newname);
 }
 
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
+int vfs_link2(struct vfsmount *mnt, struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
 {
        struct inode *inode = old_dentry->d_inode;
        unsigned max_links = dir->i_sb->s_max_links;
@@ -3563,7 +3743,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
        if (!inode)
                return -ENOENT;
 
-       error = may_create(dir, new_dentry);
+       error = may_create(mnt, dir, new_dentry);
        if (error)
                return error;
 
@@ -3597,6 +3777,13 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
                fsnotify_link(dir, inode, new_dentry);
        return error;
 }
+EXPORT_SYMBOL(vfs_link2);
+
+int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry)
+{
+       return vfs_link2(NULL, old_dentry, dir, new_dentry);
+}
+EXPORT_SYMBOL(vfs_link);
 
 /*
  * Hardlinks are often used in delicate situations.  We avoid
@@ -3650,10 +3837,11 @@ retry:
        error = security_path_link(old_path.dentry, &new_path, new_dentry);
        if (error)
                goto out_dput;
-       error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry);
+       error = vfs_link2(old_path.mnt, old_path.dentry, new_path.dentry->d_inode, new_dentry);
 out_dput:
        done_path_create(&new_path, new_dentry);
        if (retry_estale(error, how)) {
+               path_put(&old_path);
                how |= LOOKUP_REVAL;
                goto retry;
        }
@@ -3695,8 +3883,9 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
  *        ->i_mutex on parents, which works but leads to some truly excessive
  *        locking].
  */
-static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
-                         struct inode *new_dir, struct dentry *new_dentry)
+static int vfs_rename_dir(struct vfsmount *mnt,
+              struct inode *old_dir, struct dentry *old_dentry,
+              struct inode *new_dir, struct dentry *new_dentry)
 {
        int error = 0;
        struct inode *target = new_dentry->d_inode;
@@ -3707,7 +3896,7 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
         * we'll need to flip '..'.
         */
        if (new_dir != old_dir) {
-               error = inode_permission(old_dentry->d_inode, MAY_WRITE);
+               error = inode_permission2(mnt, old_dentry->d_inode, MAY_WRITE);
                if (error)
                        return error;
        }
@@ -3782,43 +3971,52 @@ out:
        return error;
 }
 
-int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+int vfs_rename2(struct vfsmount *mnt,
+              struct inode *old_dir, struct dentry *old_dentry,
               struct inode *new_dir, struct dentry *new_dentry)
 {
        int error;
        int is_dir = S_ISDIR(old_dentry->d_inode->i_mode);
-       const unsigned char *old_name;
+       struct name_snapshot old_name;
 
        if (old_dentry->d_inode == new_dentry->d_inode)
                return 0;
  
-       error = may_delete(old_dir, old_dentry, is_dir);
+       error = may_delete(mnt, old_dir, old_dentry, is_dir);
        if (error)
                return error;
 
        if (!new_dentry->d_inode)
-               error = may_create(new_dir, new_dentry);
+               error = may_create(mnt, new_dir, new_dentry);
        else
-               error = may_delete(new_dir, new_dentry, is_dir);
+               error = may_delete(mnt, new_dir, new_dentry, is_dir);
        if (error)
                return error;
 
        if (!old_dir->i_op->rename)
                return -EPERM;
 
-       old_name = fsnotify_oldname_init(old_dentry->d_name.name);
+       take_dentry_name_snapshot(&old_name, old_dentry);
 
        if (is_dir)
-               error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry);
+               error = vfs_rename_dir(mnt, old_dir,old_dentry,new_dir,new_dentry);
        else
                error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry);
        if (!error)
-               fsnotify_move(old_dir, new_dir, old_name, is_dir,
+               fsnotify_move(old_dir, new_dir, old_name.name, is_dir,
                              new_dentry->d_inode, old_dentry);
-       fsnotify_oldname_free(old_name);
+       release_dentry_name_snapshot(&old_name);
 
        return error;
 }
+EXPORT_SYMBOL(vfs_rename2);
+
+int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+              struct inode *new_dir, struct dentry *new_dentry)
+{
+       return vfs_rename2(NULL, old_dir, old_dentry, new_dir, new_dentry);
+}
+EXPORT_SYMBOL(vfs_rename);
 
 SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
                int, newdfd, const char __user *, newname)
@@ -3901,7 +4099,7 @@ retry:
                                     &newnd.path, new_dentry);
        if (error)
                goto exit5;
-       error = vfs_rename(old_dir->d_inode, old_dentry,
+       error = vfs_rename2(oldnd.path.mnt, old_dir->d_inode, old_dentry,
                                   new_dir->d_inode, new_dentry);
 exit5:
        dput(new_dentry);
@@ -4075,7 +4273,6 @@ EXPORT_SYMBOL(follow_down);
 EXPORT_SYMBOL(follow_up);
 EXPORT_SYMBOL(get_write_access); /* nfsd */
 EXPORT_SYMBOL(lock_rename);
-EXPORT_SYMBOL(lookup_one_len);
 EXPORT_SYMBOL(page_follow_link_light);
 EXPORT_SYMBOL(page_put_link);
 EXPORT_SYMBOL(page_readlink);
@@ -4084,18 +4281,9 @@ EXPORT_SYMBOL(page_symlink);
 EXPORT_SYMBOL(page_symlink_inode_operations);
 EXPORT_SYMBOL(kern_path);
 EXPORT_SYMBOL(vfs_path_lookup);
-EXPORT_SYMBOL(inode_permission);
 EXPORT_SYMBOL(unlock_rename);
-EXPORT_SYMBOL(vfs_create);
 EXPORT_SYMBOL(vfs_follow_link);
-EXPORT_SYMBOL(vfs_link);
-EXPORT_SYMBOL(vfs_mkdir);
-EXPORT_SYMBOL(vfs_mknod);
 EXPORT_SYMBOL(generic_permission);
 EXPORT_SYMBOL(vfs_readlink);
-EXPORT_SYMBOL(vfs_rename);
-EXPORT_SYMBOL(vfs_rmdir);
-EXPORT_SYMBOL(vfs_symlink);
-EXPORT_SYMBOL(vfs_unlink);
 EXPORT_SYMBOL(dentry_unhash);
 EXPORT_SYMBOL(generic_readlink);