summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2013-11-27 13:05:47 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2013-11-27 13:05:50 +1100
commit2392ba280e498b196dcb164697061cbd1d857876 (patch)
treeaf0cdd2eb5761838be02e702628df0f580faf561
parent726f7f0ca15c310a908f65866c83eba61eef9be0 (diff)
parent40216baa0101ec7ac9ce7c4f4f4d684f3a85eb93 (diff)
Merge remote-tracking branch 'userns/for-next'
Conflicts: fs/dcache.c fs/fuse/dir.c fs/mount.h fs/namei.c fs/namespace.c
-rw-r--r--fs/afs/dir.c3
-rw-r--r--fs/dcache.c80
-rw-r--r--fs/fuse/dir.c5
-rw-r--r--fs/gfs2/dentry.c4
-rw-r--r--fs/mount.h3
-rw-r--r--fs/namei.c61
-rw-r--r--fs/namespace.c30
-rw-r--r--fs/nfs/dir.c5
-rw-r--r--fs/sysfs/dir.c9
-rw-r--r--include/linux/dcache.h3
10 files changed, 113 insertions, 90 deletions
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 529300327f45..3756d4fe129f 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -683,8 +683,7 @@ not_found:
out_bad:
/* don't unhash if we have submounts */
- if (check_submounts_and_drop(dentry) != 0)
- goto out_skip;
+ shrink_submounts_and_drop(dentry);
_debug("dropping dentry %s/%s",
parent->d_name.name, dentry->d_name.name);
diff --git a/fs/dcache.c b/fs/dcache.c
index 4bdb300b16e2..9042a3291d95 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1213,7 +1213,7 @@ int d_set_mounted(struct dentry *dentry)
int ret = -ENOENT;
write_seqlock(&rename_lock);
for (p = dentry->d_parent; !IS_ROOT(p); p = p->d_parent) {
- /* Need exclusion wrt. check_submounts_and_drop() */
+ /* Need exclusion wrt. shrink_submounts_and_drop() */
spin_lock(&p->d_lock);
if (unlikely(d_unhashed(p))) {
spin_unlock(&p->d_lock);
@@ -1403,70 +1403,56 @@ void shrink_dcache_for_umount(struct super_block *sb)
}
}
-static enum d_walk_ret check_and_collect(void *_data, struct dentry *dentry)
+struct detach_data {
+ struct dentry *found;
+};
+static enum d_walk_ret do_detach_submounts(void *ptr, struct dentry *dentry)
{
- struct select_data *data = _data;
+ struct detach_data *data = ptr;
- if (d_mountpoint(dentry)) {
- data->found = -EBUSY;
- return D_WALK_QUIT;
- }
+ if (d_mountpoint(dentry))
+ data->found = dentry;
- return select_collect(_data, dentry);
-}
-
-static void check_and_drop(void *_data)
-{
- struct select_data *data = _data;
-
- if (d_mountpoint(data->start))
- data->found = -EBUSY;
- if (!data->found)
- __d_drop(data->start);
+ return data->found ? D_WALK_QUIT : D_WALK_CONTINUE;
}
/**
- * check_submounts_and_drop - prune dcache, check for submounts and drop
+ * detach_submounts - check for submounts and detach them.
*
- * All done as a single atomic operation relative to has_unlinked_ancestor().
- * Returns 0 if successfully unhashed @parent. If there were submounts then
- * return -EBUSY.
+ * @dentry: dentry to find mount points under.
*
- * @dentry: dentry to prune and drop
+ * If dentry or any of it's children is a mount point detach those mounts.
*/
-int check_submounts_and_drop(struct dentry *dentry)
+void detach_submounts(struct dentry *dentry)
{
- int ret = 0;
-
- /* Negative dentries can be dropped without further checks */
- if (!dentry->d_inode) {
- d_drop(dentry);
- goto out;
- }
-
+ struct detach_data data;
for (;;) {
- struct select_data data;
+ data.found = NULL;
+ d_walk(dentry, &data, do_detach_submounts, NULL);
- INIT_LIST_HEAD(&data.dispose);
- data.start = dentry;
- data.found = 0;
-
- d_walk(dentry, &data, check_and_collect, check_and_drop);
- ret = data.found;
-
- if (!list_empty(&data.dispose))
- shrink_dentry_list(&data.dispose);
-
- if (ret <= 0)
+ if (!data.found)
break;
+ detach_mounts(data.found);
cond_resched();
}
+ detach_mounts(dentry);
+}
-out:
- return ret;
+/**
+ * shrink_submounts_and_drop - detach submounts, prune dcache, and drop
+ *
+ * All done as a single atomic operation reletaive to d_set_mounted().
+ *
+ * @dentry: dentry to detach, prune and drop
+ */
+void shrink_submounts_and_drop(struct dentry *dentry)
+{
+ d_drop(dentry);
+ detach_submounts(dentry);
+ shrink_dcache_parent(dentry);
}
-EXPORT_SYMBOL(check_submounts_and_drop);
+EXPORT_SYMBOL(shrink_submounts_and_drop);
/**
* __d_alloc - allocate a dcache entry
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index c3eb2c46c8f1..5412bcfe310e 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -264,8 +264,9 @@ out:
invalid:
ret = 0;
- if (!(flags & LOOKUP_RCU) && check_submounts_and_drop(entry) != 0)
- ret = 1;
+ if (!(flags & LOOKUP_RCU))
+ shrink_submounts_and_drop(entry);
+
goto out;
}
diff --git a/fs/gfs2/dentry.c b/fs/gfs2/dentry.c
index d3a5d4e29ba5..2ecc2b873829 100644
--- a/fs/gfs2/dentry.c
+++ b/fs/gfs2/dentry.c
@@ -93,9 +93,7 @@ invalid_gunlock:
if (!had_lock)
gfs2_glock_dq_uninit(&d_gh);
invalid:
- if (check_submounts_and_drop(dentry) != 0)
- goto valid;
-
+ shrink_submounts_and_drop(dentry);
dput(parent);
return 0;
diff --git a/fs/mount.h b/fs/mount.h
index d64c594be6c4..fdab824c180f 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -21,6 +21,7 @@ struct mnt_pcp {
struct mountpoint {
struct list_head m_hash;
struct dentry *m_dentry;
+ struct list_head m_list;
int m_count;
};
@@ -48,6 +49,7 @@ struct mount {
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
+ struct list_head mnt_mp_list; /* list mounts with the same mountpoint */
#ifdef CONFIG_FSNOTIFY
struct hlist_head mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
@@ -79,6 +81,7 @@ static inline int is_mounted(struct vfsmount *mnt)
extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
+extern void detach_mounts(struct dentry *dentry);
extern bool legitimize_mnt(struct vfsmount *, unsigned);
diff --git a/fs/namei.c b/fs/namei.c
index 8f77a8cea289..dc038ace9e07 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3518,6 +3518,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);
+ rcu_read_unlock();
+
+ return is_covered;
+}
+
int vfs_rmdir(struct inode *dir, struct dentry *dentry)
{
int error = may_delete(dir, dentry, 1);
@@ -3531,10 +3545,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;
@@ -3546,6 +3556,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);
@@ -3593,6 +3604,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;
@@ -3647,17 +3661,15 @@ int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate
return -EPERM;
mutex_lock(&target->i_mutex);
- if (d_mountpoint(dentry))
- error = -EBUSY;
- else {
- error = security_inode_unlink(dir, dentry);
+ error = security_inode_unlink(dir, dentry);
+ if (!error) {
+ error = try_break_deleg(target, delegated_inode);
+ if (error)
+ goto out;
+ error = dir->i_op->unlink(dir, dentry);
if (!error) {
- error = try_break_deleg(target, delegated_inode);
- if (error)
- goto out;
- error = dir->i_op->unlink(dir, dentry);
- if (!error)
- dont_mount(dentry);
+ dont_mount(dentry);
+ detach_mounts(dentry);
}
}
out:
@@ -3711,6 +3723,9 @@ retry_deleg:
inode = dentry->d_inode;
if (d_is_negative(dentry))
goto slashes;
+ error = -EBUSY;
+ if (covered(nd.path.mnt, dentry))
+ goto exit2;
ihold(inode);
error = security_path_unlink(&nd.path, dentry);
if (error)
@@ -4022,10 +4037,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)
@@ -4040,6 +4051,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)
@@ -4066,10 +4078,6 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
dget(new_dentry);
lock_two_nondirectories(source, target);
- error = -EBUSY;
- if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
- goto out;
-
error = try_break_deleg(source, delegated_inode);
if (error)
goto out;
@@ -4082,8 +4090,10 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_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:
@@ -4230,6 +4240,11 @@ retry_deleg:
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);
diff --git a/fs/namespace.c b/fs/namespace.c
index ac2ce8a766e1..2fe3b61ad658 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -195,6 +195,7 @@ static struct mount *alloc_vfsmnt(const char *name)
INIT_LIST_HEAD(&mnt->mnt_share);
INIT_LIST_HEAD(&mnt->mnt_slave_list);
INIT_LIST_HEAD(&mnt->mnt_slave);
+ INIT_LIST_HEAD(&mnt->mnt_mp_list);
#ifdef CONFIG_FSNOTIFY
INIT_HLIST_HEAD(&mnt->mnt_fsnotify_marks);
#endif
@@ -660,6 +661,7 @@ static struct mountpoint *new_mountpoint(struct dentry *dentry)
mp->m_dentry = dentry;
mp->m_count = 1;
list_add(&mp->m_hash, chain);
+ INIT_LIST_HEAD(&mp->m_list);
return mp;
}
@@ -667,6 +669,7 @@ static void put_mountpoint(struct mountpoint *mp)
{
if (!--mp->m_count) {
struct dentry *dentry = mp->m_dentry;
+ BUG_ON(!list_empty(&mp->m_list));
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_MOUNTED;
spin_unlock(&dentry->d_lock);
@@ -713,6 +716,7 @@ static void detach_mnt(struct mount *mnt, struct path *old_path)
mnt->mnt_mountpoint = mnt->mnt.mnt_root;
list_del_init(&mnt->mnt_child);
list_del_init(&mnt->mnt_hash);
+ list_del_init(&mnt->mnt_mp_list);
put_mountpoint(mnt->mnt_mp);
mnt->mnt_mp = NULL;
}
@@ -729,6 +733,7 @@ void mnt_set_mountpoint(struct mount *mnt,
child_mnt->mnt_mountpoint = dget(mp->m_dentry);
child_mnt->mnt_parent = mnt;
child_mnt->mnt_mp = mp;
+ list_add_tail(&child_mnt->mnt_mp_list, &mp->m_list);
}
/*
@@ -1211,6 +1216,7 @@ void umount_tree(struct mount *mnt, int how)
p->mnt.mnt_flags |= MNT_SYNC_UMOUNT;
list_del_init(&p->mnt_child);
if (mnt_has_parent(p)) {
+ list_del_init(&p->mnt_mp_list);
put_mountpoint(p->mnt_mp);
/* move the reference to mountpoint into ->mnt_ex_mountpoint */
p->mnt_ex_mountpoint.dentry = p->mnt_mountpoint;
@@ -1318,6 +1324,30 @@ static int do_umount(struct mount *mnt, int flags)
return retval;
}
+void detach_mounts(struct dentry *dentry)
+{
+ struct mountpoint *mp;
+ struct mount *mnt;
+
+ namespace_lock();
+ if (!d_mountpoint(dentry))
+ goto out_unlock;
+
+ mp = new_mountpoint(dentry);
+ if (IS_ERR(mp))
+ goto out_unlock;
+
+ lock_mount_hash();
+ while (!list_empty(&mp->m_list)) {
+ mnt = list_first_entry(&mp->m_list, struct mount, mnt_mp_list);
+ umount_tree(mnt, 1);
+ }
+ unlock_mount_hash();
+ put_mountpoint(mp);
+out_unlock:
+ namespace_unlock();
+}
+
/*
* Is the caller allowed to modify his namespace?
*/
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 812154aff981..96addb8ae992 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1134,10 +1134,7 @@ out_zap_parent:
if (IS_ROOT(dentry))
goto out_valid;
}
- /* If we have submounts, don't unhash ! */
- if (check_submounts_and_drop(dentry) != 0)
- goto out_valid;
-
+ shrink_submounts_and_drop(dentry);
dput(parent);
dfprintk(LOOKUPCACHE, "NFS: %s(%pd2) is invalid\n",
__func__, dentry);
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c
index 5e73d6626e50..70ed97106fc9 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -309,7 +309,6 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags)
}
mutex_unlock(&sysfs_mutex);
-out_valid:
return 1;
out_bad:
/* Remove the dentry from the dcache hashes.
@@ -323,13 +322,7 @@ out_bad:
* to the dcache hashes.
*/
mutex_unlock(&sysfs_mutex);
-
- /* If we have submounts we must allow the vfs caches
- * to lie about the state of the filesystem to prevent
- * leaks and other nasty things.
- */
- if (check_submounts_and_drop(dentry) != 0)
- goto out_valid;
+ shrink_submounts_and_drop(dentry);
return 0;
}
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 57e87e749a48..7631b2064714 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -263,7 +263,8 @@ extern void d_prune_aliases(struct inode *);
/* test whether we have any submounts in a subdir tree */
extern int have_submounts(struct dentry *);
-extern int check_submounts_and_drop(struct dentry *);
+extern void detach_submounts(struct dentry *dentry);
+extern void shrink_submounts_and_drop(struct dentry *);
/*
* This adds the entry to the hash queues.