From: Thierry Reding Date: Thu, 24 Oct 2013 13:02:30 +0000 (+0200) Subject: Merge remote-tracking branch 'userns/for-next' X-Git-Tag: next-20131024~15 X-Git-Url: https://git.karo-electronics.de/?p=karo-tx-linux.git;a=commitdiff_plain;h=25691330eb62649401cfb18ef518983511f6412f;hp=645766abf88a9bde05de54c23a2f30fbdbfba746 Merge remote-tracking branch 'userns/for-next' Conflicts: fs/fuse/dir.c --- 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 d70df2e0e0da..093a68d5fec5 100644 --- a/fs/dcache.c +++ b/fs/dcache.c @@ -1372,7 +1372,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); @@ -1477,70 +1477,56 @@ void shrink_dcache_parent(struct dentry *parent) } EXPORT_SYMBOL(shrink_dcache_parent); -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; - - if (d_mountpoint(dentry)) { - data->found = -EBUSY; - return D_WALK_QUIT; - } - - return select_collect(_data, dentry); -} + struct detach_data *data = ptr; -static void check_and_drop(void *_data) -{ - struct select_data *data = _data; + if (d_mountpoint(dentry)) + data->found = dentry; - 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; - - INIT_LIST_HEAD(&data.dispose); - data.start = dentry; - data.found = 0; + data.found = NULL; + d_walk(dentry, &data, do_detach_submounts, NULL); - 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 0747f6eed598..3959aa981f17 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 64a858143ff9..7a6a2bb3f290 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; }; @@ -47,6 +48,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; @@ -77,6 +79,7 @@ static inline int is_mounted(struct vfsmount *mnt) } extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *, int); +extern void detach_mounts(struct dentry *dentry); static inline void get_mnt_ns(struct mnt_namespace *ns) { diff --git a/fs/namei.c b/fs/namei.c index caa28051e197..f892ef9b3ead 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3548,6 +3548,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); @@ -3561,10 +3575,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; @@ -3576,6 +3586,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); @@ -3623,6 +3634,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; @@ -3658,14 +3672,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); @@ -3717,6 +3729,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) @@ -3989,10 +4004,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) @@ -4007,6 +4018,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) @@ -4032,16 +4044,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: @@ -4165,6 +4175,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); diff --git a/fs/namespace.c b/fs/namespace.c index 3ee6e59ead55..ca81c46ea649 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -197,6 +197,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 @@ -636,6 +637,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; } @@ -643,6 +645,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); @@ -689,6 +692,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; } @@ -705,6 +709,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); } /* @@ -1191,6 +1196,7 @@ void umount_tree(struct mount *mnt, int propagate) list_del_init(&p->mnt_child); if (mnt_has_parent(p)) { p->mnt_parent->mnt_ghosts++; + list_del_init(&p->mnt_mp_list); put_mountpoint(p->mnt_mp); p->mnt_mp = NULL; } @@ -1289,6 +1295,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; + + br_write_lock(&vfsmount_lock); + while (!list_empty(&mp->m_list)) { + mnt = list_first_entry(&mp->m_list, struct mount, mnt_mp_list); + umount_tree(mnt, 1); + } + br_write_unlock(&vfsmount_lock); + 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 76548d81f926..c20fffac621e 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1128,10 +1128,7 @@ out_zap_parent: if (dentry->d_flags & DCACHE_DISCONNECTED) 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 eab59de47556..11837b73abbf 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c @@ -315,7 +315,6 @@ static int sysfs_dentry_revalidate(struct dentry *dentry, unsigned int flags) goto out_bad; mutex_unlock(&sysfs_mutex); -out_valid: return 1; out_bad: /* Remove the dentry from the dcache hashes. @@ -329,13 +328,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 716c3760ee39..ae57848f92d9 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -255,7 +255,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.