diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2013-11-27 13:05:47 +1100 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2013-11-27 13:05:50 +1100 |
commit | 2392ba280e498b196dcb164697061cbd1d857876 (patch) | |
tree | af0cdd2eb5761838be02e702628df0f580faf561 | |
parent | 726f7f0ca15c310a908f65866c83eba61eef9be0 (diff) | |
parent | 40216baa0101ec7ac9ce7c4f4f4d684f3a85eb93 (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.c | 3 | ||||
-rw-r--r-- | fs/dcache.c | 80 | ||||
-rw-r--r-- | fs/fuse/dir.c | 5 | ||||
-rw-r--r-- | fs/gfs2/dentry.c | 4 | ||||
-rw-r--r-- | fs/mount.h | 3 | ||||
-rw-r--r-- | fs/namei.c | 61 | ||||
-rw-r--r-- | fs/namespace.c | 30 | ||||
-rw-r--r-- | fs/nfs/dir.c | 5 | ||||
-rw-r--r-- | fs/sysfs/dir.c | 9 | ||||
-rw-r--r-- | include/linux/dcache.h | 3 |
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. |