]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/namei.c
vfs: Lazily remove mounts on unlinked files and directories. v2
[karo-tx-linux.git] / fs / namei.c
index 409a441ba2ae1c3b52f2b6d87563a9b0e80125b4..a12c1d31d4c8ab87231be38387dd3165b447ddb4 100644 (file)
@@ -660,29 +660,6 @@ static __always_inline void set_root_rcu(struct nameidata *nd)
        }
 }
 
-static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
-{
-       int ret;
-
-       if (IS_ERR(link))
-               goto fail;
-
-       if (*link == '/') {
-               set_root(nd);
-               path_put(&nd->path);
-               nd->path = nd->root;
-               path_get(&nd->root);
-               nd->flags |= LOOKUP_JUMPED;
-       }
-       nd->inode = nd->path.dentry->d_inode;
-
-       ret = link_path_walk(link, nd);
-       return ret;
-fail:
-       path_put(&nd->path);
-       return PTR_ERR(link);
-}
-
 static void path_put_conditional(struct path *path, struct nameidata *nd)
 {
        dput(path->dentry);
@@ -874,7 +851,20 @@ follow_link(struct path *link, struct nameidata *nd, void **p)
        error = 0;
        s = nd_get_link(nd);
        if (s) {
-               error = __vfs_follow_link(nd, s);
+               if (unlikely(IS_ERR(s))) {
+                       path_put(&nd->path);
+                       put_link(nd, link, *p);
+                       return PTR_ERR(s);
+               }
+               if (*s == '/') {
+                       set_root(nd);
+                       path_put(&nd->path);
+                       nd->path = nd->root;
+                       path_get(&nd->root);
+                       nd->flags |= LOOKUP_JUMPED;
+               }
+               nd->inode = nd->path.dentry->d_inode;
+               error = link_path_walk(s, nd);
                if (unlikely(error))
                        put_link(nd, link, *p);
        }
@@ -2271,12 +2261,15 @@ mountpoint_last(struct nameidata *nd, struct path *path)
                dentry = d_alloc(dir, &nd->last);
                if (!dentry) {
                        error = -ENOMEM;
+                       mutex_unlock(&dir->d_inode->i_mutex);
                        goto out;
                }
                dentry = lookup_real(dir->d_inode, dentry, nd->flags);
                error = PTR_ERR(dentry);
-               if (IS_ERR(dentry))
+               if (IS_ERR(dentry)) {
+                       mutex_unlock(&dir->d_inode->i_mutex);
                        goto out;
+               }
        }
        mutex_unlock(&dir->d_inode->i_mutex);
 
@@ -2663,6 +2656,7 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
        int acc_mode;
        int create_error = 0;
        struct dentry *const DENTRY_NOT_SET = (void *) -1UL;
+       bool excl;
 
        BUG_ON(dentry->d_inode);
 
@@ -2676,10 +2670,9 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
        if ((open_flag & O_CREAT) && !IS_POSIXACL(dir))
                mode &= ~current_umask();
 
-       if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT)) {
+       excl = (open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT);
+       if (excl)
                open_flag &= ~O_TRUNC;
-               *opened |= FILE_CREATED;
-       }
 
        /*
         * Checking write permission is tricky, bacuse we don't know if we are
@@ -2732,12 +2725,6 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
                goto out;
        }
 
-       acc_mode = op->acc_mode;
-       if (*opened & FILE_CREATED) {
-               fsnotify_create(dir, dentry);
-               acc_mode = MAY_OPEN;
-       }
-
        if (error) {    /* returned 1, that is */
                if (WARN_ON(file->f_path.dentry == DENTRY_NOT_SET)) {
                        error = -EIO;
@@ -2747,9 +2734,19 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
                        dput(dentry);
                        dentry = file->f_path.dentry;
                }
-               if (create_error && dentry->d_inode == NULL) {
-                       error = create_error;
-                       goto out;
+               if (*opened & FILE_CREATED)
+                       fsnotify_create(dir, dentry);
+               if (!dentry->d_inode) {
+                       WARN_ON(*opened & FILE_CREATED);
+                       if (create_error) {
+                               error = create_error;
+                               goto out;
+                       }
+               } else {
+                       if (excl && !(*opened & FILE_CREATED)) {
+                               error = -EEXIST;
+                               goto out;
+                       }
                }
                goto looked_up;
        }
@@ -2758,6 +2755,12 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
         * We didn't have the inode before the open, so check open permission
         * here.
         */
+       acc_mode = op->acc_mode;
+       if (*opened & FILE_CREATED) {
+               WARN_ON(!(open_flag & O_CREAT));
+               fsnotify_create(dir, dentry);
+               acc_mode = MAY_OPEN;
+       }
        error = may_open(&file->f_path, acc_mode, open_flag);
        if (error)
                fput(file);
@@ -3544,6 +3547,20 @@ void dentry_unhash(struct dentry *dentry)
        spin_unlock(&dentry->d_lock);
 }
 
+static bool covered(struct vfsmount *mnt, struct dentry *dentry)
+{
+       /* test to see if a dentry is covered with a mount in
+        * the current mount namespace.
+        */
+       bool is_covered;
+
+       rcu_read_lock();
+       is_covered = d_mountpoint(dentry) && __lookup_mnt(mnt, dentry, 1);
+       rcu_read_unlock();
+
+       return is_covered;
+}
+
 int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 {
        int error = may_delete(dir, dentry, 1);
@@ -3557,10 +3574,6 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
        dget(dentry);
        mutex_lock(&dentry->d_inode->i_mutex);
 
-       error = -EBUSY;
-       if (d_mountpoint(dentry))
-               goto out;
-
        error = security_inode_rmdir(dir, dentry);
        if (error)
                goto out;
@@ -3572,6 +3585,7 @@ int vfs_rmdir(struct inode *dir, struct dentry *dentry)
 
        dentry->d_inode->i_flags |= S_DEAD;
        dont_mount(dentry);
+       detach_mounts(dentry);
 
 out:
        mutex_unlock(&dentry->d_inode->i_mutex);
@@ -3619,6 +3633,9 @@ retry:
                error = -ENOENT;
                goto exit3;
        }
+       error = -EBUSY;
+       if (covered(nd.path.mnt, dentry))
+               goto exit3;
        error = security_path_rmdir(&nd.path, dentry);
        if (error)
                goto exit3;
@@ -3654,14 +3671,12 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry)
                return -EPERM;
 
        mutex_lock(&dentry->d_inode->i_mutex);
-       if (d_mountpoint(dentry))
-               error = -EBUSY;
-       else {
-               error = security_inode_unlink(dir, dentry);
+       error = security_inode_unlink(dir, dentry);
+       if (!error) {
+               error = dir->i_op->unlink(dir, dentry);
                if (!error) {
-                       error = dir->i_op->unlink(dir, dentry);
-                       if (!error)
-                               dont_mount(dentry);
+                       dont_mount(dentry);
+                       detach_mounts(dentry);
                }
        }
        mutex_unlock(&dentry->d_inode->i_mutex);
@@ -3713,6 +3728,9 @@ retry:
                inode = dentry->d_inode;
                if (!inode)
                        goto slashes;
+               error = -EBUSY;
+               if (covered(nd.path.mnt, dentry))
+                       goto exit2;
                ihold(inode);
                error = security_path_unlink(&nd.path, dentry);
                if (error)
@@ -3985,10 +4003,6 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
        if (target)
                mutex_lock(&target->i_mutex);
 
-       error = -EBUSY;
-       if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry))
-               goto out;
-
        error = -EMLINK;
        if (max_links && !target && new_dir != old_dir &&
            new_dir->i_nlink >= max_links)
@@ -4003,6 +4017,7 @@ static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry,
        if (target) {
                target->i_flags |= S_DEAD;
                dont_mount(new_dentry);
+               detach_mounts(new_dentry);
        }
 out:
        if (target)
@@ -4028,16 +4043,14 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
        if (target)
                mutex_lock(&target->i_mutex);
 
-       error = -EBUSY;
-       if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
-               goto out;
-
        error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
        if (error)
                goto out;
 
-       if (target)
+       if (target) {
                dont_mount(new_dentry);
+               detach_mounts(new_dentry);
+       }
        if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE))
                d_move(old_dentry, new_dentry);
 out:
@@ -4161,6 +4174,11 @@ retry:
        error = -ENOTEMPTY;
        if (new_dentry == trap)
                goto exit5;
+       error = -EBUSY;
+       if (covered(oldnd.path.mnt, old_dentry))
+               goto exit5;
+       if (covered(newnd.path.mnt, new_dentry))
+               goto exit5;
 
        error = security_path_rename(&oldnd.path, old_dentry,
                                     &newnd.path, new_dentry);
@@ -4236,11 +4254,6 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
        return res;
 }
 
-int vfs_follow_link(struct nameidata *nd, const char *link)
-{
-       return __vfs_follow_link(nd, link);
-}
-
 /* get the link contents into pagecache */
 static char *page_getlink(struct dentry * dentry, struct page **ppage)
 {
@@ -4352,7 +4365,6 @@ 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);