![David Howells](/assets/img/avatar_default.png)
Add a dentry op (d_manage) to permit a filesystem to hold a process and make it sleep when it tries to transit away from one of that filesystem's directories during a pathwalk. The operation is keyed off a new dentry flag (DCACHE_MANAGE_TRANSIT). The filesystem is allowed to be selective about which processes it holds and which it permits to continue on or prohibits from transiting from each flagged directory. This will allow autofs to hold up client processes whilst letting its userspace daemon through to maintain the directory or the stuff behind it or mounted upon it. The ->d_manage() dentry operation: int (*d_manage)(struct path *path, bool mounting_here); takes a pointer to the directory about to be transited away from and a flag indicating whether the transit is undertaken by do_add_mount() or do_move_mount() skipping through a pile of filesystems mounted on a mountpoint. It should return 0 if successful and to let the process continue on its way; -EISDIR to prohibit the caller from skipping to overmounted filesystems or automounting, and to use this directory; or some other error code to return to the user. ->d_manage() is called with namespace_sem writelocked if mounting_here is true and no other locks held, so it may sleep. However, if mounting_here is true, it may not initiate or wait for a mount or unmount upon the parameter directory, even if the act is actually performed by userspace. Within fs/namei.c, follow_managed() is extended to check with d_manage() first on each managed directory, before transiting away from it or attempting to automount upon it. follow_down() is renamed follow_down_one() and should only be used where the filesystem deliberately intends to avoid management steps (e.g. autofs). A new follow_down() is added that incorporates the loop done by all other callers of follow_down() (do_add/move_mount(), autofs and NFSD; whilst AFS, NFS and CIFS do use it, their use is removed by converting them to use d_automount()). The new follow_down() calls d_manage() as appropriate. It also takes an extra parameter to indicate if it is being called from mount code (with namespace_sem writelocked) which it passes to d_manage(). follow_down() ignores automount points so that it can be used to mount on them. __follow_mount_rcu() is made to abort rcu-walk mode if it hits a directory with DCACHE_MANAGE_TRANSIT set on the basis that we're probably going to have to sleep. It would be possible to enter d_manage() in rcu-walk mode too, and have that determine whether to abort or not itself. That would allow the autofs daemon to continue on in rcu-walk mode. Note that DCACHE_MANAGE_TRANSIT on a directory should be cleared when it isn't required as every tranist from that directory will cause d_manage() to be invoked. It can always be set again when necessary. ========================== WHAT THIS MEANS FOR AUTOFS ========================== Autofs currently uses the lookup() inode op and the d_revalidate() dentry op to trigger the automounting of indirect mounts, and both of these can be called with i_mutex held. autofs knows that the i_mutex will be held by the caller in lookup(), and so can drop it before invoking the daemon - but this isn't so for d_revalidate(), since the lock is only held on _some_ of the code paths that call it. This means that autofs can't risk dropping i_mutex from its d_revalidate() function before it calls the daemon. The bug could manifest itself as, for example, a process that's trying to validate an automount dentry that gets made to wait because that dentry is expired and needs cleaning up: mkdir S ffffffff8014e05a 0 32580 24956 Call Trace: [<ffffffff885371fd>] :autofs4:autofs4_wait+0x674/0x897 [<ffffffff80127f7d>] avc_has_perm+0x46/0x58 [<ffffffff8009fdcf>] autoremove_wake_function+0x0/0x2e [<ffffffff88537be6>] :autofs4:autofs4_expire_wait+0x41/0x6b [<ffffffff88535cfc>] :autofs4:autofs4_revalidate+0x91/0x149 [<ffffffff80036d96>] __lookup_hash+0xa0/0x12f [<ffffffff80057a2f>] lookup_create+0x46/0x80 [<ffffffff800e6e31>] sys_mkdirat+0x56/0xe4 versus the automount daemon which wants to remove that dentry, but can't because the normal process is holding the i_mutex lock: automount D ffffffff8014e05a 0 32581 1 32561 Call Trace: [<ffffffff80063c3f>] __mutex_lock_slowpath+0x60/0x9b [<ffffffff8000ccf1>] do_path_lookup+0x2ca/0x2f1 [<ffffffff80063c89>] .text.lock.mutex+0xf/0x14 [<ffffffff800e6d55>] do_rmdir+0x77/0xde [<ffffffff8005d229>] tracesys+0x71/0xe0 [<ffffffff8005d28d>] tracesys+0xd5/0xe0 which means that the system is deadlocked. This patch allows autofs to hold up normal processes whilst the daemon goes ahead and does things to the dentry tree behind the automouter point without risking a deadlock as almost no locks are held in d_manage() and none in d_automount(). Signed-off-by: David Howells <dhowells@redhat.com> Was-Acked-by: Ian Kent <raven@themaw.net> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
2599 lines
62 KiB
C
2599 lines
62 KiB
C
/*
|
|
* linux/fs/namespace.c
|
|
*
|
|
* (C) Copyright Al Viro 2000, 2001
|
|
* Released under GPL v2.
|
|
*
|
|
* Based on code from fs/super.c, copyright Linus Torvalds and others.
|
|
* Heavily rewritten.
|
|
*/
|
|
|
|
#include <linux/syscalls.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/acct.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/mnt_namespace.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/nsproxy.h>
|
|
#include <linux/security.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/ramfs.h>
|
|
#include <linux/log2.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/fs_struct.h>
|
|
#include <linux/fsnotify.h>
|
|
#include <asm/uaccess.h>
|
|
#include <asm/unistd.h>
|
|
#include "pnode.h"
|
|
#include "internal.h"
|
|
|
|
#define HASH_SHIFT ilog2(PAGE_SIZE / sizeof(struct list_head))
|
|
#define HASH_SIZE (1UL << HASH_SHIFT)
|
|
|
|
static int event;
|
|
static DEFINE_IDA(mnt_id_ida);
|
|
static DEFINE_IDA(mnt_group_ida);
|
|
static DEFINE_SPINLOCK(mnt_id_lock);
|
|
static int mnt_id_start = 0;
|
|
static int mnt_group_start = 1;
|
|
|
|
static struct list_head *mount_hashtable __read_mostly;
|
|
static struct kmem_cache *mnt_cache __read_mostly;
|
|
static struct rw_semaphore namespace_sem;
|
|
|
|
/* /sys/fs */
|
|
struct kobject *fs_kobj;
|
|
EXPORT_SYMBOL_GPL(fs_kobj);
|
|
|
|
/*
|
|
* vfsmount lock may be taken for read to prevent changes to the
|
|
* vfsmount hash, ie. during mountpoint lookups or walking back
|
|
* up the tree.
|
|
*
|
|
* It should be taken for write in all cases where the vfsmount
|
|
* tree or hash is modified or when a vfsmount structure is modified.
|
|
*/
|
|
DEFINE_BRLOCK(vfsmount_lock);
|
|
|
|
static inline unsigned long hash(struct vfsmount *mnt, struct dentry *dentry)
|
|
{
|
|
unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES);
|
|
tmp += ((unsigned long)dentry / L1_CACHE_BYTES);
|
|
tmp = tmp + (tmp >> HASH_SHIFT);
|
|
return tmp & (HASH_SIZE - 1);
|
|
}
|
|
|
|
#define MNT_WRITER_UNDERFLOW_LIMIT -(1<<16)
|
|
|
|
/*
|
|
* allocation is serialized by namespace_sem, but we need the spinlock to
|
|
* serialize with freeing.
|
|
*/
|
|
static int mnt_alloc_id(struct vfsmount *mnt)
|
|
{
|
|
int res;
|
|
|
|
retry:
|
|
ida_pre_get(&mnt_id_ida, GFP_KERNEL);
|
|
spin_lock(&mnt_id_lock);
|
|
res = ida_get_new_above(&mnt_id_ida, mnt_id_start, &mnt->mnt_id);
|
|
if (!res)
|
|
mnt_id_start = mnt->mnt_id + 1;
|
|
spin_unlock(&mnt_id_lock);
|
|
if (res == -EAGAIN)
|
|
goto retry;
|
|
|
|
return res;
|
|
}
|
|
|
|
static void mnt_free_id(struct vfsmount *mnt)
|
|
{
|
|
int id = mnt->mnt_id;
|
|
spin_lock(&mnt_id_lock);
|
|
ida_remove(&mnt_id_ida, id);
|
|
if (mnt_id_start > id)
|
|
mnt_id_start = id;
|
|
spin_unlock(&mnt_id_lock);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new peer group ID
|
|
*
|
|
* mnt_group_ida is protected by namespace_sem
|
|
*/
|
|
static int mnt_alloc_group_id(struct vfsmount *mnt)
|
|
{
|
|
int res;
|
|
|
|
if (!ida_pre_get(&mnt_group_ida, GFP_KERNEL))
|
|
return -ENOMEM;
|
|
|
|
res = ida_get_new_above(&mnt_group_ida,
|
|
mnt_group_start,
|
|
&mnt->mnt_group_id);
|
|
if (!res)
|
|
mnt_group_start = mnt->mnt_group_id + 1;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Release a peer group ID
|
|
*/
|
|
void mnt_release_group_id(struct vfsmount *mnt)
|
|
{
|
|
int id = mnt->mnt_group_id;
|
|
ida_remove(&mnt_group_ida, id);
|
|
if (mnt_group_start > id)
|
|
mnt_group_start = id;
|
|
mnt->mnt_group_id = 0;
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for read
|
|
*/
|
|
static inline void mnt_add_count(struct vfsmount *mnt, int n)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
this_cpu_add(mnt->mnt_pcp->mnt_count, n);
|
|
#else
|
|
preempt_disable();
|
|
mnt->mnt_count += n;
|
|
preempt_enable();
|
|
#endif
|
|
}
|
|
|
|
static inline void mnt_set_count(struct vfsmount *mnt, int n)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
this_cpu_write(mnt->mnt_pcp->mnt_count, n);
|
|
#else
|
|
mnt->mnt_count = n;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for read
|
|
*/
|
|
static inline void mnt_inc_count(struct vfsmount *mnt)
|
|
{
|
|
mnt_add_count(mnt, 1);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for read
|
|
*/
|
|
static inline void mnt_dec_count(struct vfsmount *mnt)
|
|
{
|
|
mnt_add_count(mnt, -1);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
unsigned int mnt_get_count(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
unsigned int count = atomic_read(&mnt->mnt_longrefs);
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_count;
|
|
}
|
|
|
|
return count;
|
|
#else
|
|
return mnt->mnt_count;
|
|
#endif
|
|
}
|
|
|
|
struct vfsmount *alloc_vfsmnt(const char *name)
|
|
{
|
|
struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
|
|
if (mnt) {
|
|
int err;
|
|
|
|
err = mnt_alloc_id(mnt);
|
|
if (err)
|
|
goto out_free_cache;
|
|
|
|
if (name) {
|
|
mnt->mnt_devname = kstrdup(name, GFP_KERNEL);
|
|
if (!mnt->mnt_devname)
|
|
goto out_free_id;
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
mnt->mnt_pcp = alloc_percpu(struct mnt_pcp);
|
|
if (!mnt->mnt_pcp)
|
|
goto out_free_devname;
|
|
|
|
atomic_set(&mnt->mnt_longrefs, 1);
|
|
#else
|
|
mnt->mnt_count = 1;
|
|
mnt->mnt_writers = 0;
|
|
#endif
|
|
|
|
INIT_LIST_HEAD(&mnt->mnt_hash);
|
|
INIT_LIST_HEAD(&mnt->mnt_child);
|
|
INIT_LIST_HEAD(&mnt->mnt_mounts);
|
|
INIT_LIST_HEAD(&mnt->mnt_list);
|
|
INIT_LIST_HEAD(&mnt->mnt_expire);
|
|
INIT_LIST_HEAD(&mnt->mnt_share);
|
|
INIT_LIST_HEAD(&mnt->mnt_slave_list);
|
|
INIT_LIST_HEAD(&mnt->mnt_slave);
|
|
#ifdef CONFIG_FSNOTIFY
|
|
INIT_HLIST_HEAD(&mnt->mnt_fsnotify_marks);
|
|
#endif
|
|
}
|
|
return mnt;
|
|
|
|
#ifdef CONFIG_SMP
|
|
out_free_devname:
|
|
kfree(mnt->mnt_devname);
|
|
#endif
|
|
out_free_id:
|
|
mnt_free_id(mnt);
|
|
out_free_cache:
|
|
kmem_cache_free(mnt_cache, mnt);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Most r/o checks on a fs are for operations that take
|
|
* discrete amounts of time, like a write() or unlink().
|
|
* We must keep track of when those operations start
|
|
* (for permission checks) and when they end, so that
|
|
* we can determine when writes are able to occur to
|
|
* a filesystem.
|
|
*/
|
|
/*
|
|
* __mnt_is_readonly: check whether a mount is read-only
|
|
* @mnt: the mount to check for its write status
|
|
*
|
|
* This shouldn't be used directly ouside of the VFS.
|
|
* It does not guarantee that the filesystem will stay
|
|
* r/w, just that it is right *now*. This can not and
|
|
* should not be used in place of IS_RDONLY(inode).
|
|
* mnt_want/drop_write() will _keep_ the filesystem
|
|
* r/w.
|
|
*/
|
|
int __mnt_is_readonly(struct vfsmount *mnt)
|
|
{
|
|
if (mnt->mnt_flags & MNT_READONLY)
|
|
return 1;
|
|
if (mnt->mnt_sb->s_flags & MS_RDONLY)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(__mnt_is_readonly);
|
|
|
|
static inline void mnt_inc_writers(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
this_cpu_inc(mnt->mnt_pcp->mnt_writers);
|
|
#else
|
|
mnt->mnt_writers++;
|
|
#endif
|
|
}
|
|
|
|
static inline void mnt_dec_writers(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
this_cpu_dec(mnt->mnt_pcp->mnt_writers);
|
|
#else
|
|
mnt->mnt_writers--;
|
|
#endif
|
|
}
|
|
|
|
static unsigned int mnt_get_writers(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
unsigned int count = 0;
|
|
int cpu;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
count += per_cpu_ptr(mnt->mnt_pcp, cpu)->mnt_writers;
|
|
}
|
|
|
|
return count;
|
|
#else
|
|
return mnt->mnt_writers;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Most r/o checks on a fs are for operations that take
|
|
* discrete amounts of time, like a write() or unlink().
|
|
* We must keep track of when those operations start
|
|
* (for permission checks) and when they end, so that
|
|
* we can determine when writes are able to occur to
|
|
* a filesystem.
|
|
*/
|
|
/**
|
|
* mnt_want_write - get write access to a mount
|
|
* @mnt: the mount on which to take a write
|
|
*
|
|
* This tells the low-level filesystem that a write is
|
|
* about to be performed to it, and makes sure that
|
|
* writes are allowed before returning success. When
|
|
* the write operation is finished, mnt_drop_write()
|
|
* must be called. This is effectively a refcount.
|
|
*/
|
|
int mnt_want_write(struct vfsmount *mnt)
|
|
{
|
|
int ret = 0;
|
|
|
|
preempt_disable();
|
|
mnt_inc_writers(mnt);
|
|
/*
|
|
* The store to mnt_inc_writers must be visible before we pass
|
|
* MNT_WRITE_HOLD loop below, so that the slowpath can see our
|
|
* incremented count after it has set MNT_WRITE_HOLD.
|
|
*/
|
|
smp_mb();
|
|
while (mnt->mnt_flags & MNT_WRITE_HOLD)
|
|
cpu_relax();
|
|
/*
|
|
* After the slowpath clears MNT_WRITE_HOLD, mnt_is_readonly will
|
|
* be set to match its requirements. So we must not load that until
|
|
* MNT_WRITE_HOLD is cleared.
|
|
*/
|
|
smp_rmb();
|
|
if (__mnt_is_readonly(mnt)) {
|
|
mnt_dec_writers(mnt);
|
|
ret = -EROFS;
|
|
goto out;
|
|
}
|
|
out:
|
|
preempt_enable();
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mnt_want_write);
|
|
|
|
/**
|
|
* mnt_clone_write - get write access to a mount
|
|
* @mnt: the mount on which to take a write
|
|
*
|
|
* This is effectively like mnt_want_write, except
|
|
* it must only be used to take an extra write reference
|
|
* on a mountpoint that we already know has a write reference
|
|
* on it. This allows some optimisation.
|
|
*
|
|
* After finished, mnt_drop_write must be called as usual to
|
|
* drop the reference.
|
|
*/
|
|
int mnt_clone_write(struct vfsmount *mnt)
|
|
{
|
|
/* superblock may be r/o */
|
|
if (__mnt_is_readonly(mnt))
|
|
return -EROFS;
|
|
preempt_disable();
|
|
mnt_inc_writers(mnt);
|
|
preempt_enable();
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mnt_clone_write);
|
|
|
|
/**
|
|
* mnt_want_write_file - get write access to a file's mount
|
|
* @file: the file who's mount on which to take a write
|
|
*
|
|
* This is like mnt_want_write, but it takes a file and can
|
|
* do some optimisations if the file is open for write already
|
|
*/
|
|
int mnt_want_write_file(struct file *file)
|
|
{
|
|
struct inode *inode = file->f_dentry->d_inode;
|
|
if (!(file->f_mode & FMODE_WRITE) || special_file(inode->i_mode))
|
|
return mnt_want_write(file->f_path.mnt);
|
|
else
|
|
return mnt_clone_write(file->f_path.mnt);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mnt_want_write_file);
|
|
|
|
/**
|
|
* mnt_drop_write - give up write access to a mount
|
|
* @mnt: the mount on which to give up write access
|
|
*
|
|
* Tells the low-level filesystem that we are done
|
|
* performing writes to it. Must be matched with
|
|
* mnt_want_write() call above.
|
|
*/
|
|
void mnt_drop_write(struct vfsmount *mnt)
|
|
{
|
|
preempt_disable();
|
|
mnt_dec_writers(mnt);
|
|
preempt_enable();
|
|
}
|
|
EXPORT_SYMBOL_GPL(mnt_drop_write);
|
|
|
|
static int mnt_make_readonly(struct vfsmount *mnt)
|
|
{
|
|
int ret = 0;
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
mnt->mnt_flags |= MNT_WRITE_HOLD;
|
|
/*
|
|
* After storing MNT_WRITE_HOLD, we'll read the counters. This store
|
|
* should be visible before we do.
|
|
*/
|
|
smp_mb();
|
|
|
|
/*
|
|
* With writers on hold, if this value is zero, then there are
|
|
* definitely no active writers (although held writers may subsequently
|
|
* increment the count, they'll have to wait, and decrement it after
|
|
* seeing MNT_READONLY).
|
|
*
|
|
* It is OK to have counter incremented on one CPU and decremented on
|
|
* another: the sum will add up correctly. The danger would be when we
|
|
* sum up each counter, if we read a counter before it is incremented,
|
|
* but then read another CPU's count which it has been subsequently
|
|
* decremented from -- we would see more decrements than we should.
|
|
* MNT_WRITE_HOLD protects against this scenario, because
|
|
* mnt_want_write first increments count, then smp_mb, then spins on
|
|
* MNT_WRITE_HOLD, so it can't be decremented by another CPU while
|
|
* we're counting up here.
|
|
*/
|
|
if (mnt_get_writers(mnt) > 0)
|
|
ret = -EBUSY;
|
|
else
|
|
mnt->mnt_flags |= MNT_READONLY;
|
|
/*
|
|
* MNT_READONLY must become visible before ~MNT_WRITE_HOLD, so writers
|
|
* that become unheld will see MNT_READONLY.
|
|
*/
|
|
smp_wmb();
|
|
mnt->mnt_flags &= ~MNT_WRITE_HOLD;
|
|
br_write_unlock(vfsmount_lock);
|
|
return ret;
|
|
}
|
|
|
|
static void __mnt_unmake_readonly(struct vfsmount *mnt)
|
|
{
|
|
br_write_lock(vfsmount_lock);
|
|
mnt->mnt_flags &= ~MNT_READONLY;
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
|
|
void simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)
|
|
{
|
|
mnt->mnt_sb = sb;
|
|
mnt->mnt_root = dget(sb->s_root);
|
|
}
|
|
|
|
EXPORT_SYMBOL(simple_set_mnt);
|
|
|
|
void free_vfsmnt(struct vfsmount *mnt)
|
|
{
|
|
kfree(mnt->mnt_devname);
|
|
mnt_free_id(mnt);
|
|
#ifdef CONFIG_SMP
|
|
free_percpu(mnt->mnt_pcp);
|
|
#endif
|
|
kmem_cache_free(mnt_cache, mnt);
|
|
}
|
|
|
|
/*
|
|
* find the first or last mount at @dentry on vfsmount @mnt depending on
|
|
* @dir. If @dir is set return the first mount else return the last mount.
|
|
* vfsmount_lock must be held for read or write.
|
|
*/
|
|
struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
|
|
int dir)
|
|
{
|
|
struct list_head *head = mount_hashtable + hash(mnt, dentry);
|
|
struct list_head *tmp = head;
|
|
struct vfsmount *p, *found = NULL;
|
|
|
|
for (;;) {
|
|
tmp = dir ? tmp->next : tmp->prev;
|
|
p = NULL;
|
|
if (tmp == head)
|
|
break;
|
|
p = list_entry(tmp, struct vfsmount, mnt_hash);
|
|
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
|
|
found = p;
|
|
break;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* lookup_mnt increments the ref count before returning
|
|
* the vfsmount struct.
|
|
*/
|
|
struct vfsmount *lookup_mnt(struct path *path)
|
|
{
|
|
struct vfsmount *child_mnt;
|
|
|
|
br_read_lock(vfsmount_lock);
|
|
if ((child_mnt = __lookup_mnt(path->mnt, path->dentry, 1)))
|
|
mntget(child_mnt);
|
|
br_read_unlock(vfsmount_lock);
|
|
return child_mnt;
|
|
}
|
|
|
|
static inline int check_mnt(struct vfsmount *mnt)
|
|
{
|
|
return mnt->mnt_ns == current->nsproxy->mnt_ns;
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
static void touch_mnt_namespace(struct mnt_namespace *ns)
|
|
{
|
|
if (ns) {
|
|
ns->event = ++event;
|
|
wake_up_interruptible(&ns->poll);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
static void __touch_mnt_namespace(struct mnt_namespace *ns)
|
|
{
|
|
if (ns && ns->event != event) {
|
|
ns->event = event;
|
|
wake_up_interruptible(&ns->poll);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear dentry's mounted state if it has no remaining mounts.
|
|
* vfsmount_lock must be held for write.
|
|
*/
|
|
static void dentry_reset_mounted(struct vfsmount *mnt, struct dentry *dentry)
|
|
{
|
|
unsigned u;
|
|
|
|
for (u = 0; u < HASH_SIZE; u++) {
|
|
struct vfsmount *p;
|
|
|
|
list_for_each_entry(p, &mount_hashtable[u], mnt_hash) {
|
|
if (p->mnt_mountpoint == dentry)
|
|
return;
|
|
}
|
|
}
|
|
spin_lock(&dentry->d_lock);
|
|
dentry->d_flags &= ~DCACHE_MOUNTED;
|
|
spin_unlock(&dentry->d_lock);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
static void detach_mnt(struct vfsmount *mnt, struct path *old_path)
|
|
{
|
|
old_path->dentry = mnt->mnt_mountpoint;
|
|
old_path->mnt = mnt->mnt_parent;
|
|
mnt->mnt_parent = mnt;
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
list_del_init(&mnt->mnt_child);
|
|
list_del_init(&mnt->mnt_hash);
|
|
dentry_reset_mounted(old_path->mnt, old_path->dentry);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
void mnt_set_mountpoint(struct vfsmount *mnt, struct dentry *dentry,
|
|
struct vfsmount *child_mnt)
|
|
{
|
|
child_mnt->mnt_parent = mntget(mnt);
|
|
child_mnt->mnt_mountpoint = dget(dentry);
|
|
spin_lock(&dentry->d_lock);
|
|
dentry->d_flags |= DCACHE_MOUNTED;
|
|
spin_unlock(&dentry->d_lock);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
static void attach_mnt(struct vfsmount *mnt, struct path *path)
|
|
{
|
|
mnt_set_mountpoint(path->mnt, path->dentry, mnt);
|
|
list_add_tail(&mnt->mnt_hash, mount_hashtable +
|
|
hash(path->mnt, path->dentry));
|
|
list_add_tail(&mnt->mnt_child, &path->mnt->mnt_mounts);
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
*/
|
|
static void commit_tree(struct vfsmount *mnt)
|
|
{
|
|
struct vfsmount *parent = mnt->mnt_parent;
|
|
struct vfsmount *m;
|
|
LIST_HEAD(head);
|
|
struct mnt_namespace *n = parent->mnt_ns;
|
|
|
|
BUG_ON(parent == mnt);
|
|
|
|
list_add_tail(&head, &mnt->mnt_list);
|
|
list_for_each_entry(m, &head, mnt_list)
|
|
m->mnt_ns = n;
|
|
list_splice(&head, n->list.prev);
|
|
|
|
list_add_tail(&mnt->mnt_hash, mount_hashtable +
|
|
hash(parent, mnt->mnt_mountpoint));
|
|
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
|
|
touch_mnt_namespace(n);
|
|
}
|
|
|
|
static struct vfsmount *next_mnt(struct vfsmount *p, struct vfsmount *root)
|
|
{
|
|
struct list_head *next = p->mnt_mounts.next;
|
|
if (next == &p->mnt_mounts) {
|
|
while (1) {
|
|
if (p == root)
|
|
return NULL;
|
|
next = p->mnt_child.next;
|
|
if (next != &p->mnt_parent->mnt_mounts)
|
|
break;
|
|
p = p->mnt_parent;
|
|
}
|
|
}
|
|
return list_entry(next, struct vfsmount, mnt_child);
|
|
}
|
|
|
|
static struct vfsmount *skip_mnt_tree(struct vfsmount *p)
|
|
{
|
|
struct list_head *prev = p->mnt_mounts.prev;
|
|
while (prev != &p->mnt_mounts) {
|
|
p = list_entry(prev, struct vfsmount, mnt_child);
|
|
prev = p->mnt_mounts.prev;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
|
|
int flag)
|
|
{
|
|
struct super_block *sb = old->mnt_sb;
|
|
struct vfsmount *mnt = alloc_vfsmnt(old->mnt_devname);
|
|
|
|
if (mnt) {
|
|
if (flag & (CL_SLAVE | CL_PRIVATE))
|
|
mnt->mnt_group_id = 0; /* not a peer of original */
|
|
else
|
|
mnt->mnt_group_id = old->mnt_group_id;
|
|
|
|
if ((flag & CL_MAKE_SHARED) && !mnt->mnt_group_id) {
|
|
int err = mnt_alloc_group_id(mnt);
|
|
if (err)
|
|
goto out_free;
|
|
}
|
|
|
|
mnt->mnt_flags = old->mnt_flags & ~MNT_WRITE_HOLD;
|
|
atomic_inc(&sb->s_active);
|
|
mnt->mnt_sb = sb;
|
|
mnt->mnt_root = dget(root);
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
mnt->mnt_parent = mnt;
|
|
|
|
if (flag & CL_SLAVE) {
|
|
list_add(&mnt->mnt_slave, &old->mnt_slave_list);
|
|
mnt->mnt_master = old;
|
|
CLEAR_MNT_SHARED(mnt);
|
|
} else if (!(flag & CL_PRIVATE)) {
|
|
if ((flag & CL_MAKE_SHARED) || IS_MNT_SHARED(old))
|
|
list_add(&mnt->mnt_share, &old->mnt_share);
|
|
if (IS_MNT_SLAVE(old))
|
|
list_add(&mnt->mnt_slave, &old->mnt_slave);
|
|
mnt->mnt_master = old->mnt_master;
|
|
}
|
|
if (flag & CL_MAKE_SHARED)
|
|
set_mnt_shared(mnt);
|
|
|
|
/* stick the duplicate mount on the same expiry list
|
|
* as the original if that was on one */
|
|
if (flag & CL_EXPIRE) {
|
|
if (!list_empty(&old->mnt_expire))
|
|
list_add(&mnt->mnt_expire, &old->mnt_expire);
|
|
}
|
|
}
|
|
return mnt;
|
|
|
|
out_free:
|
|
free_vfsmnt(mnt);
|
|
return NULL;
|
|
}
|
|
|
|
static inline void mntfree(struct vfsmount *mnt)
|
|
{
|
|
struct super_block *sb = mnt->mnt_sb;
|
|
|
|
/*
|
|
* This probably indicates that somebody messed
|
|
* up a mnt_want/drop_write() pair. If this
|
|
* happens, the filesystem was probably unable
|
|
* to make r/w->r/o transitions.
|
|
*/
|
|
/*
|
|
* The locking used to deal with mnt_count decrement provides barriers,
|
|
* so mnt_get_writers() below is safe.
|
|
*/
|
|
WARN_ON(mnt_get_writers(mnt));
|
|
fsnotify_vfsmount_delete(mnt);
|
|
dput(mnt->mnt_root);
|
|
free_vfsmnt(mnt);
|
|
deactivate_super(sb);
|
|
}
|
|
|
|
#ifdef CONFIG_SMP
|
|
static inline void __mntput(struct vfsmount *mnt, int longrefs)
|
|
{
|
|
if (!longrefs) {
|
|
put_again:
|
|
br_read_lock(vfsmount_lock);
|
|
if (likely(atomic_read(&mnt->mnt_longrefs))) {
|
|
mnt_dec_count(mnt);
|
|
br_read_unlock(vfsmount_lock);
|
|
return;
|
|
}
|
|
br_read_unlock(vfsmount_lock);
|
|
} else {
|
|
BUG_ON(!atomic_read(&mnt->mnt_longrefs));
|
|
if (atomic_add_unless(&mnt->mnt_longrefs, -1, 1))
|
|
return;
|
|
}
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
if (!longrefs)
|
|
mnt_dec_count(mnt);
|
|
else
|
|
atomic_dec(&mnt->mnt_longrefs);
|
|
if (mnt_get_count(mnt)) {
|
|
br_write_unlock(vfsmount_lock);
|
|
return;
|
|
}
|
|
if (unlikely(mnt->mnt_pinned)) {
|
|
mnt_add_count(mnt, mnt->mnt_pinned + 1);
|
|
mnt->mnt_pinned = 0;
|
|
br_write_unlock(vfsmount_lock);
|
|
acct_auto_close_mnt(mnt);
|
|
goto put_again;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
mntfree(mnt);
|
|
}
|
|
#else
|
|
static inline void __mntput(struct vfsmount *mnt, int longrefs)
|
|
{
|
|
put_again:
|
|
mnt_dec_count(mnt);
|
|
if (likely(mnt_get_count(mnt)))
|
|
return;
|
|
br_write_lock(vfsmount_lock);
|
|
if (unlikely(mnt->mnt_pinned)) {
|
|
mnt_add_count(mnt, mnt->mnt_pinned + 1);
|
|
mnt->mnt_pinned = 0;
|
|
br_write_unlock(vfsmount_lock);
|
|
acct_auto_close_mnt(mnt);
|
|
goto put_again;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
mntfree(mnt);
|
|
}
|
|
#endif
|
|
|
|
static void mntput_no_expire(struct vfsmount *mnt)
|
|
{
|
|
__mntput(mnt, 0);
|
|
}
|
|
|
|
void mntput(struct vfsmount *mnt)
|
|
{
|
|
if (mnt) {
|
|
/* avoid cacheline pingpong, hope gcc doesn't get "smart" */
|
|
if (unlikely(mnt->mnt_expiry_mark))
|
|
mnt->mnt_expiry_mark = 0;
|
|
__mntput(mnt, 0);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(mntput);
|
|
|
|
struct vfsmount *mntget(struct vfsmount *mnt)
|
|
{
|
|
if (mnt)
|
|
mnt_inc_count(mnt);
|
|
return mnt;
|
|
}
|
|
EXPORT_SYMBOL(mntget);
|
|
|
|
void mntput_long(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
if (mnt) {
|
|
/* avoid cacheline pingpong, hope gcc doesn't get "smart" */
|
|
if (unlikely(mnt->mnt_expiry_mark))
|
|
mnt->mnt_expiry_mark = 0;
|
|
__mntput(mnt, 1);
|
|
}
|
|
#else
|
|
mntput(mnt);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(mntput_long);
|
|
|
|
struct vfsmount *mntget_long(struct vfsmount *mnt)
|
|
{
|
|
#ifdef CONFIG_SMP
|
|
if (mnt)
|
|
atomic_inc(&mnt->mnt_longrefs);
|
|
return mnt;
|
|
#else
|
|
return mntget(mnt);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(mntget_long);
|
|
|
|
void mnt_pin(struct vfsmount *mnt)
|
|
{
|
|
br_write_lock(vfsmount_lock);
|
|
mnt->mnt_pinned++;
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
EXPORT_SYMBOL(mnt_pin);
|
|
|
|
void mnt_unpin(struct vfsmount *mnt)
|
|
{
|
|
br_write_lock(vfsmount_lock);
|
|
if (mnt->mnt_pinned) {
|
|
mnt_inc_count(mnt);
|
|
mnt->mnt_pinned--;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
EXPORT_SYMBOL(mnt_unpin);
|
|
|
|
static inline void mangle(struct seq_file *m, const char *s)
|
|
{
|
|
seq_escape(m, s, " \t\n\\");
|
|
}
|
|
|
|
/*
|
|
* Simple .show_options callback for filesystems which don't want to
|
|
* implement more complex mount option showing.
|
|
*
|
|
* See also save_mount_options().
|
|
*/
|
|
int generic_show_options(struct seq_file *m, struct vfsmount *mnt)
|
|
{
|
|
const char *options;
|
|
|
|
rcu_read_lock();
|
|
options = rcu_dereference(mnt->mnt_sb->s_options);
|
|
|
|
if (options != NULL && options[0]) {
|
|
seq_putc(m, ',');
|
|
mangle(m, options);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(generic_show_options);
|
|
|
|
/*
|
|
* If filesystem uses generic_show_options(), this function should be
|
|
* called from the fill_super() callback.
|
|
*
|
|
* The .remount_fs callback usually needs to be handled in a special
|
|
* way, to make sure, that previous options are not overwritten if the
|
|
* remount fails.
|
|
*
|
|
* Also note, that if the filesystem's .remount_fs function doesn't
|
|
* reset all options to their default value, but changes only newly
|
|
* given options, then the displayed options will not reflect reality
|
|
* any more.
|
|
*/
|
|
void save_mount_options(struct super_block *sb, char *options)
|
|
{
|
|
BUG_ON(sb->s_options);
|
|
rcu_assign_pointer(sb->s_options, kstrdup(options, GFP_KERNEL));
|
|
}
|
|
EXPORT_SYMBOL(save_mount_options);
|
|
|
|
void replace_mount_options(struct super_block *sb, char *options)
|
|
{
|
|
char *old = sb->s_options;
|
|
rcu_assign_pointer(sb->s_options, options);
|
|
if (old) {
|
|
synchronize_rcu();
|
|
kfree(old);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(replace_mount_options);
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
/* iterator */
|
|
static void *m_start(struct seq_file *m, loff_t *pos)
|
|
{
|
|
struct proc_mounts *p = m->private;
|
|
|
|
down_read(&namespace_sem);
|
|
return seq_list_start(&p->ns->list, *pos);
|
|
}
|
|
|
|
static void *m_next(struct seq_file *m, void *v, loff_t *pos)
|
|
{
|
|
struct proc_mounts *p = m->private;
|
|
|
|
return seq_list_next(v, &p->ns->list, pos);
|
|
}
|
|
|
|
static void m_stop(struct seq_file *m, void *v)
|
|
{
|
|
up_read(&namespace_sem);
|
|
}
|
|
|
|
int mnt_had_events(struct proc_mounts *p)
|
|
{
|
|
struct mnt_namespace *ns = p->ns;
|
|
int res = 0;
|
|
|
|
br_read_lock(vfsmount_lock);
|
|
if (p->event != ns->event) {
|
|
p->event = ns->event;
|
|
res = 1;
|
|
}
|
|
br_read_unlock(vfsmount_lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
struct proc_fs_info {
|
|
int flag;
|
|
const char *str;
|
|
};
|
|
|
|
static int show_sb_opts(struct seq_file *m, struct super_block *sb)
|
|
{
|
|
static const struct proc_fs_info fs_info[] = {
|
|
{ MS_SYNCHRONOUS, ",sync" },
|
|
{ MS_DIRSYNC, ",dirsync" },
|
|
{ MS_MANDLOCK, ",mand" },
|
|
{ 0, NULL }
|
|
};
|
|
const struct proc_fs_info *fs_infop;
|
|
|
|
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
|
|
if (sb->s_flags & fs_infop->flag)
|
|
seq_puts(m, fs_infop->str);
|
|
}
|
|
|
|
return security_sb_show_options(m, sb);
|
|
}
|
|
|
|
static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt)
|
|
{
|
|
static const struct proc_fs_info mnt_info[] = {
|
|
{ MNT_NOSUID, ",nosuid" },
|
|
{ MNT_NODEV, ",nodev" },
|
|
{ MNT_NOEXEC, ",noexec" },
|
|
{ MNT_NOATIME, ",noatime" },
|
|
{ MNT_NODIRATIME, ",nodiratime" },
|
|
{ MNT_RELATIME, ",relatime" },
|
|
{ 0, NULL }
|
|
};
|
|
const struct proc_fs_info *fs_infop;
|
|
|
|
for (fs_infop = mnt_info; fs_infop->flag; fs_infop++) {
|
|
if (mnt->mnt_flags & fs_infop->flag)
|
|
seq_puts(m, fs_infop->str);
|
|
}
|
|
}
|
|
|
|
static void show_type(struct seq_file *m, struct super_block *sb)
|
|
{
|
|
mangle(m, sb->s_type->name);
|
|
if (sb->s_subtype && sb->s_subtype[0]) {
|
|
seq_putc(m, '.');
|
|
mangle(m, sb->s_subtype);
|
|
}
|
|
}
|
|
|
|
static int show_vfsmnt(struct seq_file *m, void *v)
|
|
{
|
|
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
|
|
int err = 0;
|
|
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
|
|
|
|
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
|
|
seq_putc(m, ' ');
|
|
seq_path(m, &mnt_path, " \t\n\\");
|
|
seq_putc(m, ' ');
|
|
show_type(m, mnt->mnt_sb);
|
|
seq_puts(m, __mnt_is_readonly(mnt) ? " ro" : " rw");
|
|
err = show_sb_opts(m, mnt->mnt_sb);
|
|
if (err)
|
|
goto out;
|
|
show_mnt_opts(m, mnt);
|
|
if (mnt->mnt_sb->s_op->show_options)
|
|
err = mnt->mnt_sb->s_op->show_options(m, mnt);
|
|
seq_puts(m, " 0 0\n");
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
const struct seq_operations mounts_op = {
|
|
.start = m_start,
|
|
.next = m_next,
|
|
.stop = m_stop,
|
|
.show = show_vfsmnt
|
|
};
|
|
|
|
static int show_mountinfo(struct seq_file *m, void *v)
|
|
{
|
|
struct proc_mounts *p = m->private;
|
|
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
|
|
struct super_block *sb = mnt->mnt_sb;
|
|
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
|
|
struct path root = p->root;
|
|
int err = 0;
|
|
|
|
seq_printf(m, "%i %i %u:%u ", mnt->mnt_id, mnt->mnt_parent->mnt_id,
|
|
MAJOR(sb->s_dev), MINOR(sb->s_dev));
|
|
seq_dentry(m, mnt->mnt_root, " \t\n\\");
|
|
seq_putc(m, ' ');
|
|
seq_path_root(m, &mnt_path, &root, " \t\n\\");
|
|
if (root.mnt != p->root.mnt || root.dentry != p->root.dentry) {
|
|
/*
|
|
* Mountpoint is outside root, discard that one. Ugly,
|
|
* but less so than trying to do that in iterator in a
|
|
* race-free way (due to renames).
|
|
*/
|
|
return SEQ_SKIP;
|
|
}
|
|
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
|
|
show_mnt_opts(m, mnt);
|
|
|
|
/* Tagged fields ("foo:X" or "bar") */
|
|
if (IS_MNT_SHARED(mnt))
|
|
seq_printf(m, " shared:%i", mnt->mnt_group_id);
|
|
if (IS_MNT_SLAVE(mnt)) {
|
|
int master = mnt->mnt_master->mnt_group_id;
|
|
int dom = get_dominating_id(mnt, &p->root);
|
|
seq_printf(m, " master:%i", master);
|
|
if (dom && dom != master)
|
|
seq_printf(m, " propagate_from:%i", dom);
|
|
}
|
|
if (IS_MNT_UNBINDABLE(mnt))
|
|
seq_puts(m, " unbindable");
|
|
|
|
/* Filesystem specific data */
|
|
seq_puts(m, " - ");
|
|
show_type(m, sb);
|
|
seq_putc(m, ' ');
|
|
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
|
|
seq_puts(m, sb->s_flags & MS_RDONLY ? " ro" : " rw");
|
|
err = show_sb_opts(m, sb);
|
|
if (err)
|
|
goto out;
|
|
if (sb->s_op->show_options)
|
|
err = sb->s_op->show_options(m, mnt);
|
|
seq_putc(m, '\n');
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
const struct seq_operations mountinfo_op = {
|
|
.start = m_start,
|
|
.next = m_next,
|
|
.stop = m_stop,
|
|
.show = show_mountinfo,
|
|
};
|
|
|
|
static int show_vfsstat(struct seq_file *m, void *v)
|
|
{
|
|
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
|
|
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
|
|
int err = 0;
|
|
|
|
/* device */
|
|
if (mnt->mnt_devname) {
|
|
seq_puts(m, "device ");
|
|
mangle(m, mnt->mnt_devname);
|
|
} else
|
|
seq_puts(m, "no device");
|
|
|
|
/* mount point */
|
|
seq_puts(m, " mounted on ");
|
|
seq_path(m, &mnt_path, " \t\n\\");
|
|
seq_putc(m, ' ');
|
|
|
|
/* file system type */
|
|
seq_puts(m, "with fstype ");
|
|
show_type(m, mnt->mnt_sb);
|
|
|
|
/* optional statistics */
|
|
if (mnt->mnt_sb->s_op->show_stats) {
|
|
seq_putc(m, ' ');
|
|
err = mnt->mnt_sb->s_op->show_stats(m, mnt);
|
|
}
|
|
|
|
seq_putc(m, '\n');
|
|
return err;
|
|
}
|
|
|
|
const struct seq_operations mountstats_op = {
|
|
.start = m_start,
|
|
.next = m_next,
|
|
.stop = m_stop,
|
|
.show = show_vfsstat,
|
|
};
|
|
#endif /* CONFIG_PROC_FS */
|
|
|
|
/**
|
|
* may_umount_tree - check if a mount tree is busy
|
|
* @mnt: root of mount tree
|
|
*
|
|
* This is called to check if a tree of mounts has any
|
|
* open files, pwds, chroots or sub mounts that are
|
|
* busy.
|
|
*/
|
|
int may_umount_tree(struct vfsmount *mnt)
|
|
{
|
|
int actual_refs = 0;
|
|
int minimum_refs = 0;
|
|
struct vfsmount *p;
|
|
|
|
/* write lock needed for mnt_get_count */
|
|
br_write_lock(vfsmount_lock);
|
|
for (p = mnt; p; p = next_mnt(p, mnt)) {
|
|
actual_refs += mnt_get_count(p);
|
|
minimum_refs += 2;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
|
|
if (actual_refs > minimum_refs)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
EXPORT_SYMBOL(may_umount_tree);
|
|
|
|
/**
|
|
* may_umount - check if a mount point is busy
|
|
* @mnt: root of mount
|
|
*
|
|
* This is called to check if a mount point has any
|
|
* open files, pwds, chroots or sub mounts. If the
|
|
* mount has sub mounts this will return busy
|
|
* regardless of whether the sub mounts are busy.
|
|
*
|
|
* Doesn't take quota and stuff into account. IOW, in some cases it will
|
|
* give false negatives. The main reason why it's here is that we need
|
|
* a non-destructive way to look for easily umountable filesystems.
|
|
*/
|
|
int may_umount(struct vfsmount *mnt)
|
|
{
|
|
int ret = 1;
|
|
down_read(&namespace_sem);
|
|
br_write_lock(vfsmount_lock);
|
|
if (propagate_mount_busy(mnt, 2))
|
|
ret = 0;
|
|
br_write_unlock(vfsmount_lock);
|
|
up_read(&namespace_sem);
|
|
return ret;
|
|
}
|
|
|
|
EXPORT_SYMBOL(may_umount);
|
|
|
|
void release_mounts(struct list_head *head)
|
|
{
|
|
struct vfsmount *mnt;
|
|
while (!list_empty(head)) {
|
|
mnt = list_first_entry(head, struct vfsmount, mnt_hash);
|
|
list_del_init(&mnt->mnt_hash);
|
|
if (mnt->mnt_parent != mnt) {
|
|
struct dentry *dentry;
|
|
struct vfsmount *m;
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
dentry = mnt->mnt_mountpoint;
|
|
m = mnt->mnt_parent;
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
mnt->mnt_parent = mnt;
|
|
m->mnt_ghosts--;
|
|
br_write_unlock(vfsmount_lock);
|
|
dput(dentry);
|
|
mntput(m);
|
|
}
|
|
mntput_long(mnt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* vfsmount lock must be held for write
|
|
* namespace_sem must be held for write
|
|
*/
|
|
void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
|
|
{
|
|
struct vfsmount *p;
|
|
|
|
for (p = mnt; p; p = next_mnt(p, mnt))
|
|
list_move(&p->mnt_hash, kill);
|
|
|
|
if (propagate)
|
|
propagate_umount(kill);
|
|
|
|
list_for_each_entry(p, kill, mnt_hash) {
|
|
list_del_init(&p->mnt_expire);
|
|
list_del_init(&p->mnt_list);
|
|
__touch_mnt_namespace(p->mnt_ns);
|
|
p->mnt_ns = NULL;
|
|
list_del_init(&p->mnt_child);
|
|
if (p->mnt_parent != p) {
|
|
p->mnt_parent->mnt_ghosts++;
|
|
dentry_reset_mounted(p->mnt_parent, p->mnt_mountpoint);
|
|
}
|
|
change_mnt_propagation(p, MS_PRIVATE);
|
|
}
|
|
}
|
|
|
|
static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts);
|
|
|
|
static int do_umount(struct vfsmount *mnt, int flags)
|
|
{
|
|
struct super_block *sb = mnt->mnt_sb;
|
|
int retval;
|
|
LIST_HEAD(umount_list);
|
|
|
|
retval = security_sb_umount(mnt, flags);
|
|
if (retval)
|
|
return retval;
|
|
|
|
/*
|
|
* Allow userspace to request a mountpoint be expired rather than
|
|
* unmounting unconditionally. Unmount only happens if:
|
|
* (1) the mark is already set (the mark is cleared by mntput())
|
|
* (2) the usage count == 1 [parent vfsmount] + 1 [sys_umount]
|
|
*/
|
|
if (flags & MNT_EXPIRE) {
|
|
if (mnt == current->fs->root.mnt ||
|
|
flags & (MNT_FORCE | MNT_DETACH))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* probably don't strictly need the lock here if we examined
|
|
* all race cases, but it's a slowpath.
|
|
*/
|
|
br_write_lock(vfsmount_lock);
|
|
if (mnt_get_count(mnt) != 2) {
|
|
br_write_lock(vfsmount_lock);
|
|
return -EBUSY;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
|
|
if (!xchg(&mnt->mnt_expiry_mark, 1))
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/*
|
|
* If we may have to abort operations to get out of this
|
|
* mount, and they will themselves hold resources we must
|
|
* allow the fs to do things. In the Unix tradition of
|
|
* 'Gee thats tricky lets do it in userspace' the umount_begin
|
|
* might fail to complete on the first run through as other tasks
|
|
* must return, and the like. Thats for the mount program to worry
|
|
* about for the moment.
|
|
*/
|
|
|
|
if (flags & MNT_FORCE && sb->s_op->umount_begin) {
|
|
sb->s_op->umount_begin(sb);
|
|
}
|
|
|
|
/*
|
|
* No sense to grab the lock for this test, but test itself looks
|
|
* somewhat bogus. Suggestions for better replacement?
|
|
* Ho-hum... In principle, we might treat that as umount + switch
|
|
* to rootfs. GC would eventually take care of the old vfsmount.
|
|
* Actually it makes sense, especially if rootfs would contain a
|
|
* /reboot - static binary that would close all descriptors and
|
|
* call reboot(9). Then init(8) could umount root and exec /reboot.
|
|
*/
|
|
if (mnt == current->fs->root.mnt && !(flags & MNT_DETACH)) {
|
|
/*
|
|
* Special case for "unmounting" root ...
|
|
* we just try to remount it readonly.
|
|
*/
|
|
down_write(&sb->s_umount);
|
|
if (!(sb->s_flags & MS_RDONLY))
|
|
retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
|
|
up_write(&sb->s_umount);
|
|
return retval;
|
|
}
|
|
|
|
down_write(&namespace_sem);
|
|
br_write_lock(vfsmount_lock);
|
|
event++;
|
|
|
|
if (!(flags & MNT_DETACH))
|
|
shrink_submounts(mnt, &umount_list);
|
|
|
|
retval = -EBUSY;
|
|
if (flags & MNT_DETACH || !propagate_mount_busy(mnt, 2)) {
|
|
if (!list_empty(&mnt->mnt_list))
|
|
umount_tree(mnt, 1, &umount_list);
|
|
retval = 0;
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
up_write(&namespace_sem);
|
|
release_mounts(&umount_list);
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Now umount can handle mount points as well as block devices.
|
|
* This is important for filesystems which use unnamed block devices.
|
|
*
|
|
* We now support a flag for forced unmount like the other 'big iron'
|
|
* unixes. Our API is identical to OSF/1 to avoid making a mess of AMD
|
|
*/
|
|
|
|
SYSCALL_DEFINE2(umount, char __user *, name, int, flags)
|
|
{
|
|
struct path path;
|
|
int retval;
|
|
int lookup_flags = 0;
|
|
|
|
if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW))
|
|
return -EINVAL;
|
|
|
|
if (!(flags & UMOUNT_NOFOLLOW))
|
|
lookup_flags |= LOOKUP_FOLLOW;
|
|
|
|
retval = user_path_at(AT_FDCWD, name, lookup_flags, &path);
|
|
if (retval)
|
|
goto out;
|
|
retval = -EINVAL;
|
|
if (path.dentry != path.mnt->mnt_root)
|
|
goto dput_and_out;
|
|
if (!check_mnt(path.mnt))
|
|
goto dput_and_out;
|
|
|
|
retval = -EPERM;
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
goto dput_and_out;
|
|
|
|
retval = do_umount(path.mnt, flags);
|
|
dput_and_out:
|
|
/* we mustn't call path_put() as that would clear mnt_expiry_mark */
|
|
dput(path.dentry);
|
|
mntput_no_expire(path.mnt);
|
|
out:
|
|
return retval;
|
|
}
|
|
|
|
#ifdef __ARCH_WANT_SYS_OLDUMOUNT
|
|
|
|
/*
|
|
* The 2.0 compatible umount. No flags.
|
|
*/
|
|
SYSCALL_DEFINE1(oldumount, char __user *, name)
|
|
{
|
|
return sys_umount(name, 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
static int mount_is_safe(struct path *path)
|
|
{
|
|
if (capable(CAP_SYS_ADMIN))
|
|
return 0;
|
|
return -EPERM;
|
|
#ifdef notyet
|
|
if (S_ISLNK(path->dentry->d_inode->i_mode))
|
|
return -EPERM;
|
|
if (path->dentry->d_inode->i_mode & S_ISVTX) {
|
|
if (current_uid() != path->dentry->d_inode->i_uid)
|
|
return -EPERM;
|
|
}
|
|
if (inode_permission(path->dentry->d_inode, MAY_WRITE))
|
|
return -EPERM;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
|
|
int flag)
|
|
{
|
|
struct vfsmount *res, *p, *q, *r, *s;
|
|
struct path path;
|
|
|
|
if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(mnt))
|
|
return NULL;
|
|
|
|
res = q = clone_mnt(mnt, dentry, flag);
|
|
if (!q)
|
|
goto Enomem;
|
|
q->mnt_mountpoint = mnt->mnt_mountpoint;
|
|
|
|
p = mnt;
|
|
list_for_each_entry(r, &mnt->mnt_mounts, mnt_child) {
|
|
if (!is_subdir(r->mnt_mountpoint, dentry))
|
|
continue;
|
|
|
|
for (s = r; s; s = next_mnt(s, r)) {
|
|
if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(s)) {
|
|
s = skip_mnt_tree(s);
|
|
continue;
|
|
}
|
|
while (p != s->mnt_parent) {
|
|
p = p->mnt_parent;
|
|
q = q->mnt_parent;
|
|
}
|
|
p = s;
|
|
path.mnt = q;
|
|
path.dentry = p->mnt_mountpoint;
|
|
q = clone_mnt(p, p->mnt_root, flag);
|
|
if (!q)
|
|
goto Enomem;
|
|
br_write_lock(vfsmount_lock);
|
|
list_add_tail(&q->mnt_list, &res->mnt_list);
|
|
attach_mnt(q, &path);
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
}
|
|
return res;
|
|
Enomem:
|
|
if (res) {
|
|
LIST_HEAD(umount_list);
|
|
br_write_lock(vfsmount_lock);
|
|
umount_tree(res, 0, &umount_list);
|
|
br_write_unlock(vfsmount_lock);
|
|
release_mounts(&umount_list);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct vfsmount *collect_mounts(struct path *path)
|
|
{
|
|
struct vfsmount *tree;
|
|
down_write(&namespace_sem);
|
|
tree = copy_tree(path->mnt, path->dentry, CL_COPY_ALL | CL_PRIVATE);
|
|
up_write(&namespace_sem);
|
|
return tree;
|
|
}
|
|
|
|
void drop_collected_mounts(struct vfsmount *mnt)
|
|
{
|
|
LIST_HEAD(umount_list);
|
|
down_write(&namespace_sem);
|
|
br_write_lock(vfsmount_lock);
|
|
umount_tree(mnt, 0, &umount_list);
|
|
br_write_unlock(vfsmount_lock);
|
|
up_write(&namespace_sem);
|
|
release_mounts(&umount_list);
|
|
}
|
|
|
|
int iterate_mounts(int (*f)(struct vfsmount *, void *), void *arg,
|
|
struct vfsmount *root)
|
|
{
|
|
struct vfsmount *mnt;
|
|
int res = f(root, arg);
|
|
if (res)
|
|
return res;
|
|
list_for_each_entry(mnt, &root->mnt_list, mnt_list) {
|
|
res = f(mnt, arg);
|
|
if (res)
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup_group_ids(struct vfsmount *mnt, struct vfsmount *end)
|
|
{
|
|
struct vfsmount *p;
|
|
|
|
for (p = mnt; p != end; p = next_mnt(p, mnt)) {
|
|
if (p->mnt_group_id && !IS_MNT_SHARED(p))
|
|
mnt_release_group_id(p);
|
|
}
|
|
}
|
|
|
|
static int invent_group_ids(struct vfsmount *mnt, bool recurse)
|
|
{
|
|
struct vfsmount *p;
|
|
|
|
for (p = mnt; p; p = recurse ? next_mnt(p, mnt) : NULL) {
|
|
if (!p->mnt_group_id && !IS_MNT_SHARED(p)) {
|
|
int err = mnt_alloc_group_id(p);
|
|
if (err) {
|
|
cleanup_group_ids(mnt, p);
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* @source_mnt : mount tree to be attached
|
|
* @nd : place the mount tree @source_mnt is attached
|
|
* @parent_nd : if non-null, detach the source_mnt from its parent and
|
|
* store the parent mount and mountpoint dentry.
|
|
* (done when source_mnt is moved)
|
|
*
|
|
* NOTE: in the table below explains the semantics when a source mount
|
|
* of a given type is attached to a destination mount of a given type.
|
|
* ---------------------------------------------------------------------------
|
|
* | BIND MOUNT OPERATION |
|
|
* |**************************************************************************
|
|
* | source-->| shared | private | slave | unbindable |
|
|
* | dest | | | | |
|
|
* | | | | | | |
|
|
* | v | | | | |
|
|
* |**************************************************************************
|
|
* | shared | shared (++) | shared (+) | shared(+++)| invalid |
|
|
* | | | | | |
|
|
* |non-shared| shared (+) | private | slave (*) | invalid |
|
|
* ***************************************************************************
|
|
* A bind operation clones the source mount and mounts the clone on the
|
|
* destination mount.
|
|
*
|
|
* (++) the cloned mount is propagated to all the mounts in the propagation
|
|
* tree of the destination mount and the cloned mount is added to
|
|
* the peer group of the source mount.
|
|
* (+) the cloned mount is created under the destination mount and is marked
|
|
* as shared. The cloned mount is added to the peer group of the source
|
|
* mount.
|
|
* (+++) the mount is propagated to all the mounts in the propagation tree
|
|
* of the destination mount and the cloned mount is made slave
|
|
* of the same master as that of the source mount. The cloned mount
|
|
* is marked as 'shared and slave'.
|
|
* (*) the cloned mount is made a slave of the same master as that of the
|
|
* source mount.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* | MOVE MOUNT OPERATION |
|
|
* |**************************************************************************
|
|
* | source-->| shared | private | slave | unbindable |
|
|
* | dest | | | | |
|
|
* | | | | | | |
|
|
* | v | | | | |
|
|
* |**************************************************************************
|
|
* | shared | shared (+) | shared (+) | shared(+++) | invalid |
|
|
* | | | | | |
|
|
* |non-shared| shared (+*) | private | slave (*) | unbindable |
|
|
* ***************************************************************************
|
|
*
|
|
* (+) the mount is moved to the destination. And is then propagated to
|
|
* all the mounts in the propagation tree of the destination mount.
|
|
* (+*) the mount is moved to the destination.
|
|
* (+++) the mount is moved to the destination and is then propagated to
|
|
* all the mounts belonging to the destination mount's propagation tree.
|
|
* the mount is marked as 'shared and slave'.
|
|
* (*) the mount continues to be a slave at the new location.
|
|
*
|
|
* if the source mount is a tree, the operations explained above is
|
|
* applied to each mount in the tree.
|
|
* Must be called without spinlocks held, since this function can sleep
|
|
* in allocations.
|
|
*/
|
|
static int attach_recursive_mnt(struct vfsmount *source_mnt,
|
|
struct path *path, struct path *parent_path)
|
|
{
|
|
LIST_HEAD(tree_list);
|
|
struct vfsmount *dest_mnt = path->mnt;
|
|
struct dentry *dest_dentry = path->dentry;
|
|
struct vfsmount *child, *p;
|
|
int err;
|
|
|
|
if (IS_MNT_SHARED(dest_mnt)) {
|
|
err = invent_group_ids(source_mnt, true);
|
|
if (err)
|
|
goto out;
|
|
}
|
|
err = propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list);
|
|
if (err)
|
|
goto out_cleanup_ids;
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
|
|
if (IS_MNT_SHARED(dest_mnt)) {
|
|
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
|
|
set_mnt_shared(p);
|
|
}
|
|
if (parent_path) {
|
|
detach_mnt(source_mnt, parent_path);
|
|
attach_mnt(source_mnt, path);
|
|
touch_mnt_namespace(parent_path->mnt->mnt_ns);
|
|
} else {
|
|
mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
|
|
commit_tree(source_mnt);
|
|
}
|
|
|
|
list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
|
|
list_del_init(&child->mnt_hash);
|
|
commit_tree(child);
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
|
|
return 0;
|
|
|
|
out_cleanup_ids:
|
|
if (IS_MNT_SHARED(dest_mnt))
|
|
cleanup_group_ids(source_mnt, NULL);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int graft_tree(struct vfsmount *mnt, struct path *path)
|
|
{
|
|
int err;
|
|
if (mnt->mnt_sb->s_flags & MS_NOUSER)
|
|
return -EINVAL;
|
|
|
|
if (S_ISDIR(path->dentry->d_inode->i_mode) !=
|
|
S_ISDIR(mnt->mnt_root->d_inode->i_mode))
|
|
return -ENOTDIR;
|
|
|
|
err = -ENOENT;
|
|
mutex_lock(&path->dentry->d_inode->i_mutex);
|
|
if (cant_mount(path->dentry))
|
|
goto out_unlock;
|
|
|
|
if (!d_unlinked(path->dentry))
|
|
err = attach_recursive_mnt(mnt, path, NULL);
|
|
out_unlock:
|
|
mutex_unlock(&path->dentry->d_inode->i_mutex);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Sanity check the flags to change_mnt_propagation.
|
|
*/
|
|
|
|
static int flags_to_propagation_type(int flags)
|
|
{
|
|
int type = flags & ~MS_REC;
|
|
|
|
/* Fail if any non-propagation flags are set */
|
|
if (type & ~(MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
|
|
return 0;
|
|
/* Only one propagation flag should be set */
|
|
if (!is_power_of_2(type))
|
|
return 0;
|
|
return type;
|
|
}
|
|
|
|
/*
|
|
* recursively change the type of the mountpoint.
|
|
*/
|
|
static int do_change_type(struct path *path, int flag)
|
|
{
|
|
struct vfsmount *m, *mnt = path->mnt;
|
|
int recurse = flag & MS_REC;
|
|
int type;
|
|
int err = 0;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (path->dentry != path->mnt->mnt_root)
|
|
return -EINVAL;
|
|
|
|
type = flags_to_propagation_type(flag);
|
|
if (!type)
|
|
return -EINVAL;
|
|
|
|
down_write(&namespace_sem);
|
|
if (type == MS_SHARED) {
|
|
err = invent_group_ids(mnt, recurse);
|
|
if (err)
|
|
goto out_unlock;
|
|
}
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
|
|
change_mnt_propagation(m, type);
|
|
br_write_unlock(vfsmount_lock);
|
|
|
|
out_unlock:
|
|
up_write(&namespace_sem);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* do loopback mount.
|
|
*/
|
|
static int do_loopback(struct path *path, char *old_name,
|
|
int recurse)
|
|
{
|
|
struct path old_path;
|
|
struct vfsmount *mnt = NULL;
|
|
int err = mount_is_safe(path);
|
|
if (err)
|
|
return err;
|
|
if (!old_name || !*old_name)
|
|
return -EINVAL;
|
|
err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
|
|
if (err)
|
|
return err;
|
|
|
|
down_write(&namespace_sem);
|
|
err = -EINVAL;
|
|
if (IS_MNT_UNBINDABLE(old_path.mnt))
|
|
goto out;
|
|
|
|
if (!check_mnt(path->mnt) || !check_mnt(old_path.mnt))
|
|
goto out;
|
|
|
|
err = -ENOMEM;
|
|
if (recurse)
|
|
mnt = copy_tree(old_path.mnt, old_path.dentry, 0);
|
|
else
|
|
mnt = clone_mnt(old_path.mnt, old_path.dentry, 0);
|
|
|
|
if (!mnt)
|
|
goto out;
|
|
|
|
err = graft_tree(mnt, path);
|
|
if (err) {
|
|
LIST_HEAD(umount_list);
|
|
|
|
br_write_lock(vfsmount_lock);
|
|
umount_tree(mnt, 0, &umount_list);
|
|
br_write_unlock(vfsmount_lock);
|
|
release_mounts(&umount_list);
|
|
}
|
|
|
|
out:
|
|
up_write(&namespace_sem);
|
|
path_put(&old_path);
|
|
return err;
|
|
}
|
|
|
|
static int change_mount_flags(struct vfsmount *mnt, int ms_flags)
|
|
{
|
|
int error = 0;
|
|
int readonly_request = 0;
|
|
|
|
if (ms_flags & MS_RDONLY)
|
|
readonly_request = 1;
|
|
if (readonly_request == __mnt_is_readonly(mnt))
|
|
return 0;
|
|
|
|
if (readonly_request)
|
|
error = mnt_make_readonly(mnt);
|
|
else
|
|
__mnt_unmake_readonly(mnt);
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* change filesystem flags. dir should be a physical root of filesystem.
|
|
* If you've mounted a non-root directory somewhere and want to do remount
|
|
* on it - tough luck.
|
|
*/
|
|
static int do_remount(struct path *path, int flags, int mnt_flags,
|
|
void *data)
|
|
{
|
|
int err;
|
|
struct super_block *sb = path->mnt->mnt_sb;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
if (!check_mnt(path->mnt))
|
|
return -EINVAL;
|
|
|
|
if (path->dentry != path->mnt->mnt_root)
|
|
return -EINVAL;
|
|
|
|
down_write(&sb->s_umount);
|
|
if (flags & MS_BIND)
|
|
err = change_mount_flags(path->mnt, flags);
|
|
else
|
|
err = do_remount_sb(sb, flags, data, 0);
|
|
if (!err) {
|
|
br_write_lock(vfsmount_lock);
|
|
mnt_flags |= path->mnt->mnt_flags & MNT_PROPAGATION_MASK;
|
|
path->mnt->mnt_flags = mnt_flags;
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
up_write(&sb->s_umount);
|
|
if (!err) {
|
|
br_write_lock(vfsmount_lock);
|
|
touch_mnt_namespace(path->mnt->mnt_ns);
|
|
br_write_unlock(vfsmount_lock);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static inline int tree_contains_unbindable(struct vfsmount *mnt)
|
|
{
|
|
struct vfsmount *p;
|
|
for (p = mnt; p; p = next_mnt(p, mnt)) {
|
|
if (IS_MNT_UNBINDABLE(p))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int do_move_mount(struct path *path, char *old_name)
|
|
{
|
|
struct path old_path, parent_path;
|
|
struct vfsmount *p;
|
|
int err = 0;
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
if (!old_name || !*old_name)
|
|
return -EINVAL;
|
|
err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
|
|
if (err)
|
|
return err;
|
|
|
|
down_write(&namespace_sem);
|
|
err = follow_down(path, true);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
err = -EINVAL;
|
|
if (!check_mnt(path->mnt) || !check_mnt(old_path.mnt))
|
|
goto out;
|
|
|
|
err = -ENOENT;
|
|
mutex_lock(&path->dentry->d_inode->i_mutex);
|
|
if (cant_mount(path->dentry))
|
|
goto out1;
|
|
|
|
if (d_unlinked(path->dentry))
|
|
goto out1;
|
|
|
|
err = -EINVAL;
|
|
if (old_path.dentry != old_path.mnt->mnt_root)
|
|
goto out1;
|
|
|
|
if (old_path.mnt == old_path.mnt->mnt_parent)
|
|
goto out1;
|
|
|
|
if (S_ISDIR(path->dentry->d_inode->i_mode) !=
|
|
S_ISDIR(old_path.dentry->d_inode->i_mode))
|
|
goto out1;
|
|
/*
|
|
* Don't move a mount residing in a shared parent.
|
|
*/
|
|
if (old_path.mnt->mnt_parent &&
|
|
IS_MNT_SHARED(old_path.mnt->mnt_parent))
|
|
goto out1;
|
|
/*
|
|
* Don't move a mount tree containing unbindable mounts to a destination
|
|
* mount which is shared.
|
|
*/
|
|
if (IS_MNT_SHARED(path->mnt) &&
|
|
tree_contains_unbindable(old_path.mnt))
|
|
goto out1;
|
|
err = -ELOOP;
|
|
for (p = path->mnt; p->mnt_parent != p; p = p->mnt_parent)
|
|
if (p == old_path.mnt)
|
|
goto out1;
|
|
|
|
err = attach_recursive_mnt(old_path.mnt, path, &parent_path);
|
|
if (err)
|
|
goto out1;
|
|
|
|
/* if the mount is moved, it should no longer be expire
|
|
* automatically */
|
|
list_del_init(&old_path.mnt->mnt_expire);
|
|
out1:
|
|
mutex_unlock(&path->dentry->d_inode->i_mutex);
|
|
out:
|
|
up_write(&namespace_sem);
|
|
if (!err)
|
|
path_put(&parent_path);
|
|
path_put(&old_path);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* create a new mount for userspace and request it to be added into the
|
|
* namespace's tree
|
|
*/
|
|
static int do_new_mount(struct path *path, char *type, int flags,
|
|
int mnt_flags, char *name, void *data)
|
|
{
|
|
struct vfsmount *mnt;
|
|
|
|
if (!type)
|
|
return -EINVAL;
|
|
|
|
/* we need capabilities... */
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
mnt = do_kern_mount(type, flags, name, data);
|
|
if (IS_ERR(mnt))
|
|
return PTR_ERR(mnt);
|
|
|
|
return do_add_mount(mnt, path, mnt_flags, NULL);
|
|
}
|
|
|
|
/*
|
|
* add a mount into a namespace's mount tree
|
|
* - provide the option of adding the new mount to an expiration list
|
|
*/
|
|
int do_add_mount(struct vfsmount *newmnt, struct path *path,
|
|
int mnt_flags, struct list_head *fslist)
|
|
{
|
|
int err;
|
|
|
|
mnt_flags &= ~(MNT_SHARED | MNT_WRITE_HOLD | MNT_INTERNAL);
|
|
|
|
down_write(&namespace_sem);
|
|
/* Something was mounted here while we slept */
|
|
err = follow_down(path, true);
|
|
if (err < 0)
|
|
goto unlock;
|
|
|
|
err = -EINVAL;
|
|
if (!(mnt_flags & MNT_SHRINKABLE) && !check_mnt(path->mnt))
|
|
goto unlock;
|
|
|
|
/* Refuse the same filesystem on the same mount point */
|
|
err = -EBUSY;
|
|
if (path->mnt->mnt_sb == newmnt->mnt_sb &&
|
|
path->mnt->mnt_root == path->dentry)
|
|
goto unlock;
|
|
|
|
err = -EINVAL;
|
|
if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
|
|
goto unlock;
|
|
|
|
newmnt->mnt_flags = mnt_flags;
|
|
if ((err = graft_tree(newmnt, path)))
|
|
goto unlock;
|
|
|
|
if (fslist) /* add to the specified expiration list */
|
|
list_add_tail(&newmnt->mnt_expire, fslist);
|
|
|
|
up_write(&namespace_sem);
|
|
return 0;
|
|
|
|
unlock:
|
|
up_write(&namespace_sem);
|
|
mntput_long(newmnt);
|
|
return err;
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(do_add_mount);
|
|
|
|
/*
|
|
* process a list of expirable mountpoints with the intent of discarding any
|
|
* mountpoints that aren't in use and haven't been touched since last we came
|
|
* here
|
|
*/
|
|
void mark_mounts_for_expiry(struct list_head *mounts)
|
|
{
|
|
struct vfsmount *mnt, *next;
|
|
LIST_HEAD(graveyard);
|
|
LIST_HEAD(umounts);
|
|
|
|
if (list_empty(mounts))
|
|
return;
|
|
|
|
down_write(&namespace_sem);
|
|
br_write_lock(vfsmount_lock);
|
|
|
|
/* extract from the expiration list every vfsmount that matches the
|
|
* following criteria:
|
|
* - only referenced by its parent vfsmount
|
|
* - still marked for expiry (marked on the last call here; marks are
|
|
* cleared by mntput())
|
|
*/
|
|
list_for_each_entry_safe(mnt, next, mounts, mnt_expire) {
|
|
if (!xchg(&mnt->mnt_expiry_mark, 1) ||
|
|
propagate_mount_busy(mnt, 1))
|
|
continue;
|
|
list_move(&mnt->mnt_expire, &graveyard);
|
|
}
|
|
while (!list_empty(&graveyard)) {
|
|
mnt = list_first_entry(&graveyard, struct vfsmount, mnt_expire);
|
|
touch_mnt_namespace(mnt->mnt_ns);
|
|
umount_tree(mnt, 1, &umounts);
|
|
}
|
|
br_write_unlock(vfsmount_lock);
|
|
up_write(&namespace_sem);
|
|
|
|
release_mounts(&umounts);
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(mark_mounts_for_expiry);
|
|
|
|
/*
|
|
* Ripoff of 'select_parent()'
|
|
*
|
|
* search the list of submounts for a given mountpoint, and move any
|
|
* shrinkable submounts to the 'graveyard' list.
|
|
*/
|
|
static int select_submounts(struct vfsmount *parent, struct list_head *graveyard)
|
|
{
|
|
struct vfsmount *this_parent = parent;
|
|
struct list_head *next;
|
|
int found = 0;
|
|
|
|
repeat:
|
|
next = this_parent->mnt_mounts.next;
|
|
resume:
|
|
while (next != &this_parent->mnt_mounts) {
|
|
struct list_head *tmp = next;
|
|
struct vfsmount *mnt = list_entry(tmp, struct vfsmount, mnt_child);
|
|
|
|
next = tmp->next;
|
|
if (!(mnt->mnt_flags & MNT_SHRINKABLE))
|
|
continue;
|
|
/*
|
|
* Descend a level if the d_mounts list is non-empty.
|
|
*/
|
|
if (!list_empty(&mnt->mnt_mounts)) {
|
|
this_parent = mnt;
|
|
goto repeat;
|
|
}
|
|
|
|
if (!propagate_mount_busy(mnt, 1)) {
|
|
list_move_tail(&mnt->mnt_expire, graveyard);
|
|
found++;
|
|
}
|
|
}
|
|
/*
|
|
* All done at this level ... ascend and resume the search
|
|
*/
|
|
if (this_parent != parent) {
|
|
next = this_parent->mnt_child.next;
|
|
this_parent = this_parent->mnt_parent;
|
|
goto resume;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* process a list of expirable mountpoints with the intent of discarding any
|
|
* submounts of a specific parent mountpoint
|
|
*
|
|
* vfsmount_lock must be held for write
|
|
*/
|
|
static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts)
|
|
{
|
|
LIST_HEAD(graveyard);
|
|
struct vfsmount *m;
|
|
|
|
/* extract submounts of 'mountpoint' from the expiration list */
|
|
while (select_submounts(mnt, &graveyard)) {
|
|
while (!list_empty(&graveyard)) {
|
|
m = list_first_entry(&graveyard, struct vfsmount,
|
|
mnt_expire);
|
|
touch_mnt_namespace(m->mnt_ns);
|
|
umount_tree(m, 1, umounts);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Some copy_from_user() implementations do not return the exact number of
|
|
* bytes remaining to copy on a fault. But copy_mount_options() requires that.
|
|
* Note that this function differs from copy_from_user() in that it will oops
|
|
* on bad values of `to', rather than returning a short copy.
|
|
*/
|
|
static long exact_copy_from_user(void *to, const void __user * from,
|
|
unsigned long n)
|
|
{
|
|
char *t = to;
|
|
const char __user *f = from;
|
|
char c;
|
|
|
|
if (!access_ok(VERIFY_READ, from, n))
|
|
return n;
|
|
|
|
while (n) {
|
|
if (__get_user(c, f)) {
|
|
memset(t, 0, n);
|
|
break;
|
|
}
|
|
*t++ = c;
|
|
f++;
|
|
n--;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
int copy_mount_options(const void __user * data, unsigned long *where)
|
|
{
|
|
int i;
|
|
unsigned long page;
|
|
unsigned long size;
|
|
|
|
*where = 0;
|
|
if (!data)
|
|
return 0;
|
|
|
|
if (!(page = __get_free_page(GFP_KERNEL)))
|
|
return -ENOMEM;
|
|
|
|
/* We only care that *some* data at the address the user
|
|
* gave us is valid. Just in case, we'll zero
|
|
* the remainder of the page.
|
|
*/
|
|
/* copy_from_user cannot cross TASK_SIZE ! */
|
|
size = TASK_SIZE - (unsigned long)data;
|
|
if (size > PAGE_SIZE)
|
|
size = PAGE_SIZE;
|
|
|
|
i = size - exact_copy_from_user((void *)page, data, size);
|
|
if (!i) {
|
|
free_page(page);
|
|
return -EFAULT;
|
|
}
|
|
if (i != PAGE_SIZE)
|
|
memset((char *)page + i, 0, PAGE_SIZE - i);
|
|
*where = page;
|
|
return 0;
|
|
}
|
|
|
|
int copy_mount_string(const void __user *data, char **where)
|
|
{
|
|
char *tmp;
|
|
|
|
if (!data) {
|
|
*where = NULL;
|
|
return 0;
|
|
}
|
|
|
|
tmp = strndup_user(data, PAGE_SIZE);
|
|
if (IS_ERR(tmp))
|
|
return PTR_ERR(tmp);
|
|
|
|
*where = tmp;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Flags is a 32-bit value that allows up to 31 non-fs dependent flags to
|
|
* be given to the mount() call (ie: read-only, no-dev, no-suid etc).
|
|
*
|
|
* data is a (void *) that can point to any structure up to
|
|
* PAGE_SIZE-1 bytes, which can contain arbitrary fs-dependent
|
|
* information (or be NULL).
|
|
*
|
|
* Pre-0.97 versions of mount() didn't have a flags word.
|
|
* When the flags word was introduced its top half was required
|
|
* to have the magic value 0xC0ED, and this remained so until 2.4.0-test9.
|
|
* Therefore, if this magic number is present, it carries no information
|
|
* and must be discarded.
|
|
*/
|
|
long do_mount(char *dev_name, char *dir_name, char *type_page,
|
|
unsigned long flags, void *data_page)
|
|
{
|
|
struct path path;
|
|
int retval = 0;
|
|
int mnt_flags = 0;
|
|
|
|
/* Discard magic */
|
|
if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
|
|
flags &= ~MS_MGC_MSK;
|
|
|
|
/* Basic sanity checks */
|
|
|
|
if (!dir_name || !*dir_name || !memchr(dir_name, 0, PAGE_SIZE))
|
|
return -EINVAL;
|
|
|
|
if (data_page)
|
|
((char *)data_page)[PAGE_SIZE - 1] = 0;
|
|
|
|
/* ... and get the mountpoint */
|
|
retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
|
|
if (retval)
|
|
return retval;
|
|
|
|
retval = security_sb_mount(dev_name, &path,
|
|
type_page, flags, data_page);
|
|
if (retval)
|
|
goto dput_out;
|
|
|
|
/* Default to relatime unless overriden */
|
|
if (!(flags & MS_NOATIME))
|
|
mnt_flags |= MNT_RELATIME;
|
|
|
|
/* Separate the per-mountpoint flags */
|
|
if (flags & MS_NOSUID)
|
|
mnt_flags |= MNT_NOSUID;
|
|
if (flags & MS_NODEV)
|
|
mnt_flags |= MNT_NODEV;
|
|
if (flags & MS_NOEXEC)
|
|
mnt_flags |= MNT_NOEXEC;
|
|
if (flags & MS_NOATIME)
|
|
mnt_flags |= MNT_NOATIME;
|
|
if (flags & MS_NODIRATIME)
|
|
mnt_flags |= MNT_NODIRATIME;
|
|
if (flags & MS_STRICTATIME)
|
|
mnt_flags &= ~(MNT_RELATIME | MNT_NOATIME);
|
|
if (flags & MS_RDONLY)
|
|
mnt_flags |= MNT_READONLY;
|
|
|
|
flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE | MS_BORN |
|
|
MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT |
|
|
MS_STRICTATIME);
|
|
|
|
if (flags & MS_REMOUNT)
|
|
retval = do_remount(&path, flags & ~MS_REMOUNT, mnt_flags,
|
|
data_page);
|
|
else if (flags & MS_BIND)
|
|
retval = do_loopback(&path, dev_name, flags & MS_REC);
|
|
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
|
|
retval = do_change_type(&path, flags);
|
|
else if (flags & MS_MOVE)
|
|
retval = do_move_mount(&path, dev_name);
|
|
else
|
|
retval = do_new_mount(&path, type_page, flags, mnt_flags,
|
|
dev_name, data_page);
|
|
dput_out:
|
|
path_put(&path);
|
|
return retval;
|
|
}
|
|
|
|
static struct mnt_namespace *alloc_mnt_ns(void)
|
|
{
|
|
struct mnt_namespace *new_ns;
|
|
|
|
new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL);
|
|
if (!new_ns)
|
|
return ERR_PTR(-ENOMEM);
|
|
atomic_set(&new_ns->count, 1);
|
|
new_ns->root = NULL;
|
|
INIT_LIST_HEAD(&new_ns->list);
|
|
init_waitqueue_head(&new_ns->poll);
|
|
new_ns->event = 0;
|
|
return new_ns;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new namespace structure and populate it with contents
|
|
* copied from the namespace of the passed in task structure.
|
|
*/
|
|
static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
|
|
struct fs_struct *fs)
|
|
{
|
|
struct mnt_namespace *new_ns;
|
|
struct vfsmount *rootmnt = NULL, *pwdmnt = NULL;
|
|
struct vfsmount *p, *q;
|
|
|
|
new_ns = alloc_mnt_ns();
|
|
if (IS_ERR(new_ns))
|
|
return new_ns;
|
|
|
|
down_write(&namespace_sem);
|
|
/* First pass: copy the tree topology */
|
|
new_ns->root = copy_tree(mnt_ns->root, mnt_ns->root->mnt_root,
|
|
CL_COPY_ALL | CL_EXPIRE);
|
|
if (!new_ns->root) {
|
|
up_write(&namespace_sem);
|
|
kfree(new_ns);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
br_write_lock(vfsmount_lock);
|
|
list_add_tail(&new_ns->list, &new_ns->root->mnt_list);
|
|
br_write_unlock(vfsmount_lock);
|
|
|
|
/*
|
|
* Second pass: switch the tsk->fs->* elements and mark new vfsmounts
|
|
* as belonging to new namespace. We have already acquired a private
|
|
* fs_struct, so tsk->fs->lock is not needed.
|
|
*/
|
|
p = mnt_ns->root;
|
|
q = new_ns->root;
|
|
while (p) {
|
|
q->mnt_ns = new_ns;
|
|
if (fs) {
|
|
if (p == fs->root.mnt) {
|
|
rootmnt = p;
|
|
fs->root.mnt = mntget_long(q);
|
|
}
|
|
if (p == fs->pwd.mnt) {
|
|
pwdmnt = p;
|
|
fs->pwd.mnt = mntget_long(q);
|
|
}
|
|
}
|
|
p = next_mnt(p, mnt_ns->root);
|
|
q = next_mnt(q, new_ns->root);
|
|
}
|
|
up_write(&namespace_sem);
|
|
|
|
if (rootmnt)
|
|
mntput_long(rootmnt);
|
|
if (pwdmnt)
|
|
mntput_long(pwdmnt);
|
|
|
|
return new_ns;
|
|
}
|
|
|
|
struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
|
|
struct fs_struct *new_fs)
|
|
{
|
|
struct mnt_namespace *new_ns;
|
|
|
|
BUG_ON(!ns);
|
|
get_mnt_ns(ns);
|
|
|
|
if (!(flags & CLONE_NEWNS))
|
|
return ns;
|
|
|
|
new_ns = dup_mnt_ns(ns, new_fs);
|
|
|
|
put_mnt_ns(ns);
|
|
return new_ns;
|
|
}
|
|
|
|
/**
|
|
* create_mnt_ns - creates a private namespace and adds a root filesystem
|
|
* @mnt: pointer to the new root filesystem mountpoint
|
|
*/
|
|
struct mnt_namespace *create_mnt_ns(struct vfsmount *mnt)
|
|
{
|
|
struct mnt_namespace *new_ns;
|
|
|
|
new_ns = alloc_mnt_ns();
|
|
if (!IS_ERR(new_ns)) {
|
|
mnt->mnt_ns = new_ns;
|
|
new_ns->root = mnt;
|
|
list_add(&new_ns->list, &new_ns->root->mnt_list);
|
|
}
|
|
return new_ns;
|
|
}
|
|
EXPORT_SYMBOL(create_mnt_ns);
|
|
|
|
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
|
|
char __user *, type, unsigned long, flags, void __user *, data)
|
|
{
|
|
int ret;
|
|
char *kernel_type;
|
|
char *kernel_dir;
|
|
char *kernel_dev;
|
|
unsigned long data_page;
|
|
|
|
ret = copy_mount_string(type, &kernel_type);
|
|
if (ret < 0)
|
|
goto out_type;
|
|
|
|
kernel_dir = getname(dir_name);
|
|
if (IS_ERR(kernel_dir)) {
|
|
ret = PTR_ERR(kernel_dir);
|
|
goto out_dir;
|
|
}
|
|
|
|
ret = copy_mount_string(dev_name, &kernel_dev);
|
|
if (ret < 0)
|
|
goto out_dev;
|
|
|
|
ret = copy_mount_options(data, &data_page);
|
|
if (ret < 0)
|
|
goto out_data;
|
|
|
|
ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
|
|
(void *) data_page);
|
|
|
|
free_page(data_page);
|
|
out_data:
|
|
kfree(kernel_dev);
|
|
out_dev:
|
|
putname(kernel_dir);
|
|
out_dir:
|
|
kfree(kernel_type);
|
|
out_type:
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pivot_root Semantics:
|
|
* Moves the root file system of the current process to the directory put_old,
|
|
* makes new_root as the new root file system of the current process, and sets
|
|
* root/cwd of all processes which had them on the current root to new_root.
|
|
*
|
|
* Restrictions:
|
|
* The new_root and put_old must be directories, and must not be on the
|
|
* same file system as the current process root. The put_old must be
|
|
* underneath new_root, i.e. adding a non-zero number of /.. to the string
|
|
* pointed to by put_old must yield the same directory as new_root. No other
|
|
* file system may be mounted on put_old. After all, new_root is a mountpoint.
|
|
*
|
|
* Also, the current root cannot be on the 'rootfs' (initial ramfs) filesystem.
|
|
* See Documentation/filesystems/ramfs-rootfs-initramfs.txt for alternatives
|
|
* in this situation.
|
|
*
|
|
* Notes:
|
|
* - we don't move root/cwd if they are not at the root (reason: if something
|
|
* cared enough to change them, it's probably wrong to force them elsewhere)
|
|
* - it's okay to pick a root that isn't the root of a file system, e.g.
|
|
* /nfs/my_root where /nfs is the mount point. It must be a mountpoint,
|
|
* though, so you may need to say mount --bind /nfs/my_root /nfs/my_root
|
|
* first.
|
|
*/
|
|
SYSCALL_DEFINE2(pivot_root, const char __user *, new_root,
|
|
const char __user *, put_old)
|
|
{
|
|
struct vfsmount *tmp;
|
|
struct path new, old, parent_path, root_parent, root;
|
|
int error;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
return -EPERM;
|
|
|
|
error = user_path_dir(new_root, &new);
|
|
if (error)
|
|
goto out0;
|
|
error = -EINVAL;
|
|
if (!check_mnt(new.mnt))
|
|
goto out1;
|
|
|
|
error = user_path_dir(put_old, &old);
|
|
if (error)
|
|
goto out1;
|
|
|
|
error = security_sb_pivotroot(&old, &new);
|
|
if (error) {
|
|
path_put(&old);
|
|
goto out1;
|
|
}
|
|
|
|
get_fs_root(current->fs, &root);
|
|
down_write(&namespace_sem);
|
|
mutex_lock(&old.dentry->d_inode->i_mutex);
|
|
error = -EINVAL;
|
|
if (IS_MNT_SHARED(old.mnt) ||
|
|
IS_MNT_SHARED(new.mnt->mnt_parent) ||
|
|
IS_MNT_SHARED(root.mnt->mnt_parent))
|
|
goto out2;
|
|
if (!check_mnt(root.mnt))
|
|
goto out2;
|
|
error = -ENOENT;
|
|
if (cant_mount(old.dentry))
|
|
goto out2;
|
|
if (d_unlinked(new.dentry))
|
|
goto out2;
|
|
if (d_unlinked(old.dentry))
|
|
goto out2;
|
|
error = -EBUSY;
|
|
if (new.mnt == root.mnt ||
|
|
old.mnt == root.mnt)
|
|
goto out2; /* loop, on the same file system */
|
|
error = -EINVAL;
|
|
if (root.mnt->mnt_root != root.dentry)
|
|
goto out2; /* not a mountpoint */
|
|
if (root.mnt->mnt_parent == root.mnt)
|
|
goto out2; /* not attached */
|
|
if (new.mnt->mnt_root != new.dentry)
|
|
goto out2; /* not a mountpoint */
|
|
if (new.mnt->mnt_parent == new.mnt)
|
|
goto out2; /* not attached */
|
|
/* make sure we can reach put_old from new_root */
|
|
tmp = old.mnt;
|
|
br_write_lock(vfsmount_lock);
|
|
if (tmp != new.mnt) {
|
|
for (;;) {
|
|
if (tmp->mnt_parent == tmp)
|
|
goto out3; /* already mounted on put_old */
|
|
if (tmp->mnt_parent == new.mnt)
|
|
break;
|
|
tmp = tmp->mnt_parent;
|
|
}
|
|
if (!is_subdir(tmp->mnt_mountpoint, new.dentry))
|
|
goto out3;
|
|
} else if (!is_subdir(old.dentry, new.dentry))
|
|
goto out3;
|
|
detach_mnt(new.mnt, &parent_path);
|
|
detach_mnt(root.mnt, &root_parent);
|
|
/* mount old root on put_old */
|
|
attach_mnt(root.mnt, &old);
|
|
/* mount new_root on / */
|
|
attach_mnt(new.mnt, &root_parent);
|
|
touch_mnt_namespace(current->nsproxy->mnt_ns);
|
|
br_write_unlock(vfsmount_lock);
|
|
chroot_fs_refs(&root, &new);
|
|
|
|
error = 0;
|
|
path_put(&root_parent);
|
|
path_put(&parent_path);
|
|
out2:
|
|
mutex_unlock(&old.dentry->d_inode->i_mutex);
|
|
up_write(&namespace_sem);
|
|
path_put(&root);
|
|
path_put(&old);
|
|
out1:
|
|
path_put(&new);
|
|
out0:
|
|
return error;
|
|
out3:
|
|
br_write_unlock(vfsmount_lock);
|
|
goto out2;
|
|
}
|
|
|
|
static void __init init_mount_tree(void)
|
|
{
|
|
struct vfsmount *mnt;
|
|
struct mnt_namespace *ns;
|
|
struct path root;
|
|
|
|
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
|
|
if (IS_ERR(mnt))
|
|
panic("Can't create rootfs");
|
|
|
|
ns = create_mnt_ns(mnt);
|
|
if (IS_ERR(ns))
|
|
panic("Can't allocate initial namespace");
|
|
|
|
init_task.nsproxy->mnt_ns = ns;
|
|
get_mnt_ns(ns);
|
|
|
|
root.mnt = ns->root;
|
|
root.dentry = ns->root->mnt_root;
|
|
|
|
set_fs_pwd(current->fs, &root);
|
|
set_fs_root(current->fs, &root);
|
|
}
|
|
|
|
void __init mnt_init(void)
|
|
{
|
|
unsigned u;
|
|
int err;
|
|
|
|
init_rwsem(&namespace_sem);
|
|
|
|
mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
|
|
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
|
|
|
|
mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
|
|
|
|
if (!mount_hashtable)
|
|
panic("Failed to allocate mount hash table\n");
|
|
|
|
printk("Mount-cache hash table entries: %lu\n", HASH_SIZE);
|
|
|
|
for (u = 0; u < HASH_SIZE; u++)
|
|
INIT_LIST_HEAD(&mount_hashtable[u]);
|
|
|
|
br_lock_init(vfsmount_lock);
|
|
|
|
err = sysfs_init();
|
|
if (err)
|
|
printk(KERN_WARNING "%s: sysfs_init error: %d\n",
|
|
__func__, err);
|
|
fs_kobj = kobject_create_and_add("fs", NULL);
|
|
if (!fs_kobj)
|
|
printk(KERN_WARNING "%s: kobj create error\n", __func__);
|
|
init_rootfs();
|
|
init_mount_tree();
|
|
}
|
|
|
|
void put_mnt_ns(struct mnt_namespace *ns)
|
|
{
|
|
LIST_HEAD(umount_list);
|
|
|
|
if (!atomic_dec_and_test(&ns->count))
|
|
return;
|
|
down_write(&namespace_sem);
|
|
br_write_lock(vfsmount_lock);
|
|
umount_tree(ns->root, 0, &umount_list);
|
|
br_write_unlock(vfsmount_lock);
|
|
up_write(&namespace_sem);
|
|
release_mounts(&umount_list);
|
|
kfree(ns);
|
|
}
|
|
EXPORT_SYMBOL(put_mnt_ns);
|