2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* 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/smp_lock.h>
|
|
|
|
#include <linux/init.h>
|
2006-09-29 12:58:57 +04:00
|
|
|
#include <linux/kernel.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/quotaops.h>
|
|
|
|
#include <linux/acct.h>
|
2006-01-11 23:17:46 +03:00
|
|
|
#include <linux/capability.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/module.h>
|
2006-08-15 09:43:23 +04:00
|
|
|
#include <linux/sysfs.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/seq_file.h>
|
2006-12-08 13:37:56 +03:00
|
|
|
#include <linux/mnt_namespace.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <linux/namei.h>
|
|
|
|
#include <linux/security.h>
|
|
|
|
#include <linux/mount.h>
|
2006-09-30 22:52:18 +04:00
|
|
|
#include <linux/ramfs.h>
|
2008-02-06 12:37:57 +03:00
|
|
|
#include <linux/log2.h>
|
2005-04-17 02:20:36 +04:00
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/unistd.h>
|
2005-11-08 01:19:07 +03:00
|
|
|
#include "pnode.h"
|
2007-07-16 10:41:25 +04:00
|
|
|
#include "internal.h"
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-02-06 12:37:57 +03:00
|
|
|
#define HASH_SHIFT ilog2(PAGE_SIZE / sizeof(struct list_head))
|
|
|
|
#define HASH_SIZE (1UL << HASH_SHIFT)
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/* spinlock for vfsmount related operations, inplace of dcache_lock */
|
2005-11-08 01:15:49 +03:00
|
|
|
__cacheline_aligned_in_smp DEFINE_SPINLOCK(vfsmount_lock);
|
|
|
|
|
|
|
|
static int event;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-03-26 13:37:24 +04:00
|
|
|
static struct list_head *mount_hashtable __read_mostly;
|
2006-12-07 07:33:20 +03:00
|
|
|
static struct kmem_cache *mnt_cache __read_mostly;
|
2005-11-08 01:17:51 +03:00
|
|
|
static struct rw_semaphore namespace_sem;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-01-17 09:14:23 +03:00
|
|
|
/* /sys/fs */
|
2007-10-29 23:17:23 +03:00
|
|
|
struct kobject *fs_kobj;
|
|
|
|
EXPORT_SYMBOL_GPL(fs_kobj);
|
2006-01-17 09:14:23 +03:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static inline unsigned long hash(struct vfsmount *mnt, struct dentry *dentry)
|
|
|
|
{
|
2005-11-08 01:16:09 +03:00
|
|
|
unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES);
|
|
|
|
tmp += ((unsigned long)dentry / L1_CACHE_BYTES);
|
2008-02-06 12:37:57 +03:00
|
|
|
tmp = tmp + (tmp >> HASH_SHIFT);
|
|
|
|
return tmp & (HASH_SIZE - 1);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
struct vfsmount *alloc_vfsmnt(const char *name)
|
|
|
|
{
|
2007-02-10 12:45:03 +03:00
|
|
|
struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (mnt) {
|
2005-11-08 01:16:09 +03:00
|
|
|
atomic_set(&mnt->mnt_count, 1);
|
2005-04-17 02:20:36 +04:00
|
|
|
INIT_LIST_HEAD(&mnt->mnt_hash);
|
|
|
|
INIT_LIST_HEAD(&mnt->mnt_child);
|
|
|
|
INIT_LIST_HEAD(&mnt->mnt_mounts);
|
|
|
|
INIT_LIST_HEAD(&mnt->mnt_list);
|
2005-07-08 04:57:30 +04:00
|
|
|
INIT_LIST_HEAD(&mnt->mnt_expire);
|
2005-11-08 01:19:33 +03:00
|
|
|
INIT_LIST_HEAD(&mnt->mnt_share);
|
2005-11-08 01:20:48 +03:00
|
|
|
INIT_LIST_HEAD(&mnt->mnt_slave_list);
|
|
|
|
INIT_LIST_HEAD(&mnt->mnt_slave);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (name) {
|
2005-11-08 01:16:09 +03:00
|
|
|
int size = strlen(name) + 1;
|
2005-04-17 02:20:36 +04:00
|
|
|
char *newname = kmalloc(size, GFP_KERNEL);
|
|
|
|
if (newname) {
|
|
|
|
memcpy(newname, name, size);
|
|
|
|
mnt->mnt_devname = newname;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mnt;
|
|
|
|
}
|
|
|
|
|
[PATCH] VFS: Permit filesystem to override root dentry on mount
Extend the get_sb() filesystem operation to take an extra argument that
permits the VFS to pass in the target vfsmount that defines the mountpoint.
The filesystem is then required to manually set the superblock and root dentry
pointers. For most filesystems, this should be done with simple_set_mnt()
which will set the superblock pointer and then set the root dentry to the
superblock's s_root (as per the old default behaviour).
The get_sb() op now returns an integer as there's now no need to return the
superblock pointer.
This patch permits a superblock to be implicitly shared amongst several mount
points, such as can be done with NFS to avoid potential inode aliasing. In
such a case, simple_set_mnt() would not be called, and instead the mnt_root
and mnt_sb would be set directly.
The patch also makes the following changes:
(*) the get_sb_*() convenience functions in the core kernel now take a vfsmount
pointer argument and return an integer, so most filesystems have to change
very little.
(*) If one of the convenience function is not used, then get_sb() should
normally call simple_set_mnt() to instantiate the vfsmount. This will
always return 0, and so can be tail-called from get_sb().
(*) generic_shutdown_super() now calls shrink_dcache_sb() to clean up the
dcache upon superblock destruction rather than shrink_dcache_anon().
This is required because the superblock may now have multiple trees that
aren't actually bound to s_root, but that still need to be cleaned up. The
currently called functions assume that the whole tree is rooted at s_root,
and that anonymous dentries are not the roots of trees which results in
dentries being left unculled.
However, with the way NFS superblock sharing are currently set to be
implemented, these assumptions are violated: the root of the filesystem is
simply a dummy dentry and inode (the real inode for '/' may well be
inaccessible), and all the vfsmounts are rooted on anonymous[*] dentries
with child trees.
[*] Anonymous until discovered from another tree.
(*) The documentation has been adjusted, including the additional bit of
changing ext2_* into foo_* in the documentation.
[akpm@osdl.org: convert ipath_fs, do other stuff]
Signed-off-by: David Howells <dhowells@redhat.com>
Acked-by: Al Viro <viro@zeniv.linux.org.uk>
Cc: Nathan Scott <nathans@sgi.com>
Cc: Roland Dreier <rolandd@cisco.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-06-23 13:02:57 +04:00
|
|
|
int simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)
|
|
|
|
{
|
|
|
|
mnt->mnt_sb = sb;
|
|
|
|
mnt->mnt_root = dget(sb->s_root);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(simple_set_mnt);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
void free_vfsmnt(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
kfree(mnt->mnt_devname);
|
|
|
|
kmem_cache_free(mnt_cache, mnt);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-11-08 01:20:17 +03:00
|
|
|
* 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.
|
2005-04-17 02:20:36 +04:00
|
|
|
*/
|
2005-11-08 01:20:17 +03:00
|
|
|
struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
|
|
|
|
int dir)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2005-11-08 01:16:09 +03:00
|
|
|
struct list_head *head = mount_hashtable + hash(mnt, dentry);
|
|
|
|
struct list_head *tmp = head;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct vfsmount *p, *found = NULL;
|
|
|
|
|
|
|
|
for (;;) {
|
2005-11-08 01:20:17 +03:00
|
|
|
tmp = dir ? tmp->next : tmp->prev;
|
2005-04-17 02:20:36 +04:00
|
|
|
p = NULL;
|
|
|
|
if (tmp == head)
|
|
|
|
break;
|
|
|
|
p = list_entry(tmp, struct vfsmount, mnt_hash);
|
|
|
|
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
|
2005-11-08 01:20:17 +03:00
|
|
|
found = p;
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:20:17 +03:00
|
|
|
/*
|
|
|
|
* lookup_mnt increments the ref count before returning
|
|
|
|
* the vfsmount struct.
|
|
|
|
*/
|
|
|
|
struct vfsmount *lookup_mnt(struct vfsmount *mnt, struct dentry *dentry)
|
|
|
|
{
|
|
|
|
struct vfsmount *child_mnt;
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
if ((child_mnt = __lookup_mnt(mnt, dentry, 1)))
|
|
|
|
mntget(child_mnt);
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
return child_mnt;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static inline int check_mnt(struct vfsmount *mnt)
|
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
return mnt->mnt_ns == current->nsproxy->mnt_ns;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2006-12-08 13:37:56 +03:00
|
|
|
static void touch_mnt_namespace(struct mnt_namespace *ns)
|
2005-11-08 01:15:49 +03:00
|
|
|
{
|
|
|
|
if (ns) {
|
|
|
|
ns->event = ++event;
|
|
|
|
wake_up_interruptible(&ns->poll);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-12-08 13:37:56 +03:00
|
|
|
static void __touch_mnt_namespace(struct mnt_namespace *ns)
|
2005-11-08 01:15:49 +03:00
|
|
|
{
|
|
|
|
if (ns && ns->event != event) {
|
|
|
|
ns->event = event;
|
|
|
|
wake_up_interruptible(&ns->poll);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-22 03:48:19 +03:00
|
|
|
static void detach_mnt(struct vfsmount *mnt, struct path *old_path)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-03-22 03:48:19 +03:00
|
|
|
old_path->dentry = mnt->mnt_mountpoint;
|
|
|
|
old_path->mnt = mnt->mnt_parent;
|
2005-04-17 02:20:36 +04:00
|
|
|
mnt->mnt_parent = mnt;
|
|
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
|
|
list_del_init(&mnt->mnt_child);
|
|
|
|
list_del_init(&mnt->mnt_hash);
|
2008-03-22 03:48:19 +03:00
|
|
|
old_path->dentry->d_mounted--;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2005-11-08 01:19:50 +03:00
|
|
|
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);
|
|
|
|
dentry->d_mounted++;
|
|
|
|
}
|
|
|
|
|
2008-03-22 03:48:19 +03:00
|
|
|
static void attach_mnt(struct vfsmount *mnt, struct path *path)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-03-22 03:48:19 +03:00
|
|
|
mnt_set_mountpoint(path->mnt, path->dentry, mnt);
|
2005-11-08 01:19:50 +03:00
|
|
|
list_add_tail(&mnt->mnt_hash, mount_hashtable +
|
2008-03-22 03:48:19 +03:00
|
|
|
hash(path->mnt, path->dentry));
|
|
|
|
list_add_tail(&mnt->mnt_child, &path->mnt->mnt_mounts);
|
2005-11-08 01:19:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* the caller must hold vfsmount_lock
|
|
|
|
*/
|
|
|
|
static void commit_tree(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
struct vfsmount *parent = mnt->mnt_parent;
|
|
|
|
struct vfsmount *m;
|
|
|
|
LIST_HEAD(head);
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *n = parent->mnt_ns;
|
2005-11-08 01:19:50 +03:00
|
|
|
|
|
|
|
BUG_ON(parent == mnt);
|
|
|
|
|
|
|
|
list_add_tail(&head, &mnt->mnt_list);
|
|
|
|
list_for_each_entry(m, &head, mnt_list)
|
2006-12-08 13:37:56 +03:00
|
|
|
m->mnt_ns = n;
|
2005-11-08 01:19:50 +03:00
|
|
|
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);
|
2006-12-08 13:37:56 +03:00
|
|
|
touch_mnt_namespace(n);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:21:20 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:17:22 +03:00
|
|
|
static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
|
|
|
|
int flag)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct super_block *sb = old->mnt_sb;
|
|
|
|
struct vfsmount *mnt = alloc_vfsmnt(old->mnt_devname);
|
|
|
|
|
|
|
|
if (mnt) {
|
|
|
|
mnt->mnt_flags = old->mnt_flags;
|
|
|
|
atomic_inc(&sb->s_active);
|
|
|
|
mnt->mnt_sb = sb;
|
|
|
|
mnt->mnt_root = dget(root);
|
|
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
|
|
mnt->mnt_parent = mnt;
|
2005-11-08 01:19:50 +03:00
|
|
|
|
2005-11-08 01:21:01 +03:00
|
|
|
if (flag & CL_SLAVE) {
|
|
|
|
list_add(&mnt->mnt_slave, &old->mnt_slave_list);
|
|
|
|
mnt->mnt_master = old;
|
|
|
|
CLEAR_MNT_SHARED(mnt);
|
2007-06-07 20:20:32 +04:00
|
|
|
} else if (!(flag & CL_PRIVATE)) {
|
2005-11-08 01:21:01 +03:00
|
|
|
if ((flag & CL_PROPAGATION) || 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;
|
|
|
|
}
|
2005-11-08 01:19:50 +03:00
|
|
|
if (flag & CL_MAKE_SHARED)
|
|
|
|
set_mnt_shared(mnt);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* stick the duplicate mount on the same expiry list
|
|
|
|
* as the original if that was on one */
|
2005-11-08 01:17:22 +03:00
|
|
|
if (flag & CL_EXPIRE) {
|
|
|
|
if (!list_empty(&old->mnt_expire))
|
|
|
|
list_add(&mnt->mnt_expire, &old->mnt_expire);
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
return mnt;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:13:39 +03:00
|
|
|
static inline void __mntput(struct vfsmount *mnt)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct super_block *sb = mnt->mnt_sb;
|
|
|
|
dput(mnt->mnt_root);
|
|
|
|
free_vfsmnt(mnt);
|
|
|
|
deactivate_super(sb);
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:13:39 +03:00
|
|
|
void mntput_no_expire(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
repeat:
|
|
|
|
if (atomic_dec_and_lock(&mnt->mnt_count, &vfsmount_lock)) {
|
|
|
|
if (likely(!mnt->mnt_pinned)) {
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
__mntput(mnt);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
atomic_add(mnt->mnt_pinned + 1, &mnt->mnt_count);
|
|
|
|
mnt->mnt_pinned = 0;
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
acct_auto_close_mnt(mnt);
|
|
|
|
security_sb_umount_close(mnt);
|
|
|
|
goto repeat;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(mntput_no_expire);
|
|
|
|
|
|
|
|
void mnt_pin(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
mnt->mnt_pinned++;
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(mnt_pin);
|
|
|
|
|
|
|
|
void mnt_unpin(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
if (mnt->mnt_pinned) {
|
|
|
|
atomic_inc(&mnt->mnt_count);
|
|
|
|
mnt->mnt_pinned--;
|
|
|
|
}
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(mnt_unpin);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-02-08 15:21:35 +03:00
|
|
|
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 = mnt->mnt_sb->s_options;
|
|
|
|
|
|
|
|
if (options != NULL && options[0]) {
|
|
|
|
seq_putc(m, ',');
|
|
|
|
mangle(m, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
kfree(sb->s_options);
|
|
|
|
sb->s_options = kstrdup(options, GFP_KERNEL);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(save_mount_options);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/* iterator */
|
|
|
|
static void *m_start(struct seq_file *m, loff_t *pos)
|
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *n = m->private;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_read(&namespace_sem);
|
2007-07-16 10:39:55 +04:00
|
|
|
return seq_list_start(&n->list, *pos);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void *m_next(struct seq_file *m, void *v, loff_t *pos)
|
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *n = m->private;
|
2007-07-16 10:39:55 +04:00
|
|
|
|
|
|
|
return seq_list_next(v, &n->list, pos);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void m_stop(struct seq_file *m, void *v)
|
|
|
|
{
|
2005-11-08 01:17:51 +03:00
|
|
|
up_read(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int show_vfsmnt(struct seq_file *m, void *v)
|
|
|
|
{
|
2007-07-16 10:39:55 +04:00
|
|
|
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
int err = 0;
|
|
|
|
static struct proc_fs_info {
|
|
|
|
int flag;
|
|
|
|
char *str;
|
|
|
|
} fs_info[] = {
|
|
|
|
{ MS_SYNCHRONOUS, ",sync" },
|
|
|
|
{ MS_DIRSYNC, ",dirsync" },
|
|
|
|
{ MS_MANDLOCK, ",mand" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
static struct proc_fs_info mnt_info[] = {
|
|
|
|
{ MNT_NOSUID, ",nosuid" },
|
|
|
|
{ MNT_NODEV, ",nodev" },
|
|
|
|
{ MNT_NOEXEC, ",noexec" },
|
2006-01-10 07:52:17 +03:00
|
|
|
{ MNT_NOATIME, ",noatime" },
|
|
|
|
{ MNT_NODIRATIME, ",nodiratime" },
|
2006-12-13 11:34:34 +03:00
|
|
|
{ MNT_RELATIME, ",relatime" },
|
2005-04-17 02:20:36 +04:00
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
struct proc_fs_info *fs_infop;
|
2008-02-15 06:38:43 +03:00
|
|
|
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
|
|
|
|
seq_putc(m, ' ');
|
2008-02-15 06:38:43 +03:00
|
|
|
seq_path(m, &mnt_path, " \t\n\\");
|
2005-04-17 02:20:36 +04:00
|
|
|
seq_putc(m, ' ');
|
|
|
|
mangle(m, mnt->mnt_sb->s_type->name);
|
add filesystem subtype support
There's a slight problem with filesystem type representation in fuse
based filesystems.
From the kernel's view, there are just two filesystem types: fuse and
fuseblk. From the user's view there are lots of different filesystem
types. The user is not even much concerned if the filesystem is fuse based
or not. So there's a conflict of interest in how this should be
represented in fstab, mtab and /proc/mounts.
The current scheme is to encode the real filesystem type in the mount
source. So an sshfs mount looks like this:
sshfs#user@server:/ /mnt/server fuse rw,nosuid,nodev,...
This url-ish syntax works OK for sshfs and similar filesystems. However
for block device based filesystems (ntfs-3g, zfs) it doesn't work, since
the kernel expects the mount source to be a real device name.
A possibly better scheme would be to encode the real type in the type
field as "type.subtype". So fuse mounts would look like this:
/dev/hda1 /mnt/windows fuseblk.ntfs-3g rw,...
user@server:/ /mnt/server fuse.sshfs rw,nosuid,nodev,...
This patch adds the necessary code to the kernel so that this can be
correctly displayed in /proc/mounts.
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-05-08 11:25:43 +04:00
|
|
|
if (mnt->mnt_sb->s_subtype && mnt->mnt_sb->s_subtype[0]) {
|
|
|
|
seq_putc(m, '.');
|
|
|
|
mangle(m, mnt->mnt_sb->s_subtype);
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
seq_puts(m, mnt->mnt_sb->s_flags & MS_RDONLY ? " ro" : " rw");
|
|
|
|
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
|
|
|
|
if (mnt->mnt_sb->s_flags & fs_infop->flag)
|
|
|
|
seq_puts(m, fs_infop->str);
|
|
|
|
}
|
|
|
|
for (fs_infop = mnt_info; fs_infop->flag; fs_infop++) {
|
|
|
|
if (mnt->mnt_flags & fs_infop->flag)
|
|
|
|
seq_puts(m, fs_infop->str);
|
|
|
|
}
|
|
|
|
if (mnt->mnt_sb->s_op->show_options)
|
|
|
|
err = mnt->mnt_sb->s_op->show_options(m, mnt);
|
|
|
|
seq_puts(m, " 0 0\n");
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct seq_operations mounts_op = {
|
|
|
|
.start = m_start,
|
|
|
|
.next = m_next,
|
|
|
|
.stop = m_stop,
|
|
|
|
.show = show_vfsmnt
|
|
|
|
};
|
|
|
|
|
2006-03-20 21:44:12 +03:00
|
|
|
static int show_vfsstat(struct seq_file *m, void *v)
|
|
|
|
{
|
2007-07-16 10:39:55 +04:00
|
|
|
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
|
2008-02-15 06:38:43 +03:00
|
|
|
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
|
2006-03-20 21:44:12 +03:00
|
|
|
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 ");
|
2008-02-15 06:38:43 +03:00
|
|
|
seq_path(m, &mnt_path, " \t\n\\");
|
2006-03-20 21:44:12 +03:00
|
|
|
seq_putc(m, ' ');
|
|
|
|
|
|
|
|
/* file system type */
|
|
|
|
seq_puts(m, "with fstype ");
|
|
|
|
mangle(m, mnt->mnt_sb->s_type->name);
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct seq_operations mountstats_op = {
|
|
|
|
.start = m_start,
|
|
|
|
.next = m_next,
|
|
|
|
.stop = m_stop,
|
|
|
|
.show = show_vfsstat,
|
|
|
|
};
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
{
|
2005-11-08 01:17:22 +03:00
|
|
|
int actual_refs = 0;
|
|
|
|
int minimum_refs = 0;
|
|
|
|
struct vfsmount *p;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
spin_lock(&vfsmount_lock);
|
2005-11-08 01:17:22 +03:00
|
|
|
for (p = mnt; p; p = next_mnt(p, mnt)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
actual_refs += atomic_read(&p->mnt_count);
|
|
|
|
minimum_refs += 2;
|
|
|
|
}
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
|
|
|
|
if (actual_refs > minimum_refs)
|
2006-03-27 13:14:51 +04:00
|
|
|
return 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-03-27 13:14:51 +04:00
|
|
|
return 1;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2006-03-27 13:14:51 +04:00
|
|
|
int ret = 1;
|
2005-11-08 01:20:17 +03:00
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
if (propagate_mount_busy(mnt, 2))
|
2006-03-27 13:14:51 +04:00
|
|
|
ret = 0;
|
2005-11-08 01:20:17 +03:00
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
return ret;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(may_umount);
|
|
|
|
|
2005-11-08 01:19:50 +03:00
|
|
|
void release_mounts(struct list_head *head)
|
2005-11-08 01:17:04 +03:00
|
|
|
{
|
|
|
|
struct vfsmount *mnt;
|
2006-01-08 12:03:19 +03:00
|
|
|
while (!list_empty(head)) {
|
Introduce a handy list_first_entry macro
There are many places in the kernel where the construction like
foo = list_entry(head->next, struct foo_struct, list);
are used.
The code might look more descriptive and neat if using the macro
list_first_entry(head, type, member) \
list_entry((head)->next, type, member)
Here is the macro itself and the examples of its usage in the generic code.
If it will turn out to be useful, I can prepare the set of patches to
inject in into arch-specific code, drivers, networking, etc.
Signed-off-by: Pavel Emelianov <xemul@openvz.org>
Signed-off-by: Kirill Korotaev <dev@openvz.org>
Cc: Randy Dunlap <randy.dunlap@oracle.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: Zach Brown <zach.brown@oracle.com>
Cc: Davide Libenzi <davidel@xmailserver.org>
Cc: John McCutchan <ttb@tentacle.dhs.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: john stultz <johnstul@us.ibm.com>
Cc: Ram Pai <linuxram@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-05-08 11:30:19 +04:00
|
|
|
mnt = list_first_entry(head, struct vfsmount, mnt_hash);
|
2005-11-08 01:17:04 +03:00
|
|
|
list_del_init(&mnt->mnt_hash);
|
|
|
|
if (mnt->mnt_parent != mnt) {
|
|
|
|
struct dentry *dentry;
|
|
|
|
struct vfsmount *m;
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
dentry = mnt->mnt_mountpoint;
|
|
|
|
m = mnt->mnt_parent;
|
|
|
|
mnt->mnt_mountpoint = mnt->mnt_root;
|
|
|
|
mnt->mnt_parent = mnt;
|
2008-03-22 06:59:49 +03:00
|
|
|
m->mnt_ghosts--;
|
2005-11-08 01:17:04 +03:00
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
dput(dentry);
|
|
|
|
mntput(m);
|
|
|
|
}
|
|
|
|
mntput(mnt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:20:17 +03:00
|
|
|
void umount_tree(struct vfsmount *mnt, int propagate, struct list_head *kill)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct vfsmount *p;
|
|
|
|
|
2006-06-26 11:24:40 +04:00
|
|
|
for (p = mnt; p; p = next_mnt(p, mnt))
|
|
|
|
list_move(&p->mnt_hash, kill);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:20:17 +03:00
|
|
|
if (propagate)
|
|
|
|
propagate_umount(kill);
|
|
|
|
|
2005-11-08 01:17:04 +03:00
|
|
|
list_for_each_entry(p, kill, mnt_hash) {
|
|
|
|
list_del_init(&p->mnt_expire);
|
|
|
|
list_del_init(&p->mnt_list);
|
2006-12-08 13:37:56 +03:00
|
|
|
__touch_mnt_namespace(p->mnt_ns);
|
|
|
|
p->mnt_ns = NULL;
|
2005-11-08 01:17:04 +03:00
|
|
|
list_del_init(&p->mnt_child);
|
2008-03-22 06:59:49 +03:00
|
|
|
if (p->mnt_parent != p) {
|
|
|
|
p->mnt_parent->mnt_ghosts++;
|
2006-02-01 15:53:21 +03:00
|
|
|
p->mnt_mountpoint->d_mounted--;
|
2008-03-22 06:59:49 +03:00
|
|
|
}
|
2005-11-08 01:20:17 +03:00
|
|
|
change_mnt_propagation(p, MS_PRIVATE);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-22 07:46:23 +03:00
|
|
|
static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static int do_umount(struct vfsmount *mnt, int flags)
|
|
|
|
{
|
2005-11-08 01:16:09 +03:00
|
|
|
struct super_block *sb = mnt->mnt_sb;
|
2005-04-17 02:20:36 +04:00
|
|
|
int retval;
|
2005-11-08 01:17:04 +03:00
|
|
|
LIST_HEAD(umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
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) {
|
2008-02-15 06:34:38 +03:00
|
|
|
if (mnt == current->fs->root.mnt ||
|
2005-04-17 02:20:36 +04:00
|
|
|
flags & (MNT_FORCE | MNT_DETACH))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (atomic_read(&mnt->mnt_count) != 2)
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lock_kernel();
|
2006-06-09 17:34:18 +04:00
|
|
|
if (sb->s_op->umount_begin)
|
|
|
|
sb->s_op->umount_begin(mnt, flags);
|
2005-04-17 02:20:36 +04:00
|
|
|
unlock_kernel();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2008-02-15 06:34:38 +03:00
|
|
|
if (mnt == current->fs->root.mnt && !(flags & MNT_DETACH)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* Special case for "unmounting" root ...
|
|
|
|
* we just try to remount it readonly.
|
|
|
|
*/
|
|
|
|
down_write(&sb->s_umount);
|
|
|
|
if (!(sb->s_flags & MS_RDONLY)) {
|
|
|
|
lock_kernel();
|
|
|
|
DQUOT_OFF(sb);
|
|
|
|
retval = do_remount_sb(sb, MS_RDONLY, NULL, 0);
|
|
|
|
unlock_kernel();
|
|
|
|
}
|
|
|
|
up_write(&sb->s_umount);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock(&vfsmount_lock);
|
2005-11-08 01:15:49 +03:00
|
|
|
event++;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-03-22 07:46:23 +03:00
|
|
|
if (!(flags & MNT_DETACH))
|
|
|
|
shrink_submounts(mnt, &umount_list);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
retval = -EBUSY;
|
2005-11-08 01:20:17 +03:00
|
|
|
if (flags & MNT_DETACH || !propagate_mount_busy(mnt, 2)) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!list_empty(&mnt->mnt_list))
|
2005-11-08 01:20:17 +03:00
|
|
|
umount_tree(mnt, 1, &umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
retval = 0;
|
|
|
|
}
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
if (retval)
|
|
|
|
security_sb_umount_busy(mnt);
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-11-08 01:17:04 +03:00
|
|
|
release_mounts(&umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
|
|
|
asmlinkage long sys_umount(char __user * name, int flags)
|
|
|
|
{
|
|
|
|
struct nameidata nd;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
retval = __user_walk(name, LOOKUP_FOLLOW, &nd);
|
|
|
|
if (retval)
|
|
|
|
goto out;
|
|
|
|
retval = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (nd.path.dentry != nd.path.mnt->mnt_root)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto dput_and_out;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(nd.path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto dput_and_out;
|
|
|
|
|
|
|
|
retval = -EPERM;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
goto dput_and_out;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
retval = do_umount(nd.path.mnt, flags);
|
2005-04-17 02:20:36 +04:00
|
|
|
dput_and_out:
|
2008-02-15 06:34:31 +03:00
|
|
|
/* we mustn't call path_put() as that would clear mnt_expiry_mark */
|
2008-02-15 06:34:32 +03:00
|
|
|
dput(nd.path.dentry);
|
|
|
|
mntput_no_expire(nd.path.mnt);
|
2005-04-17 02:20:36 +04:00
|
|
|
out:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef __ARCH_WANT_SYS_OLDUMOUNT
|
|
|
|
|
|
|
|
/*
|
2005-11-08 01:16:09 +03:00
|
|
|
* The 2.0 compatible umount. No flags.
|
2005-04-17 02:20:36 +04:00
|
|
|
*/
|
|
|
|
asmlinkage long sys_oldumount(char __user * name)
|
|
|
|
{
|
2005-11-08 01:16:09 +03:00
|
|
|
return sys_umount(name, 0);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int mount_is_safe(struct nameidata *nd)
|
|
|
|
{
|
|
|
|
if (capable(CAP_SYS_ADMIN))
|
|
|
|
return 0;
|
|
|
|
return -EPERM;
|
|
|
|
#ifdef notyet
|
2008-02-15 06:34:32 +03:00
|
|
|
if (S_ISLNK(nd->path.dentry->d_inode->i_mode))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EPERM;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (nd->path.dentry->d_inode->i_mode & S_ISVTX) {
|
|
|
|
if (current->uid != nd->path.dentry->d_inode->i_uid)
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EPERM;
|
|
|
|
}
|
2005-11-09 08:35:04 +03:00
|
|
|
if (vfs_permission(nd, MAY_WRITE))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EPERM;
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
static int lives_below_in_same_fs(struct dentry *d, struct dentry *dentry)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
while (1) {
|
|
|
|
if (d == dentry)
|
|
|
|
return 1;
|
|
|
|
if (d == NULL || d == d->d_parent)
|
|
|
|
return 0;
|
|
|
|
d = d->d_parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:19:50 +03:00
|
|
|
struct vfsmount *copy_tree(struct vfsmount *mnt, struct dentry *dentry,
|
2005-11-08 01:17:22 +03:00
|
|
|
int flag)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct vfsmount *res, *p, *q, *r, *s;
|
2008-03-22 03:48:19 +03:00
|
|
|
struct path path;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:21:20 +03:00
|
|
|
if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(mnt))
|
|
|
|
return NULL;
|
|
|
|
|
2005-11-08 01:17:22 +03:00
|
|
|
res = q = clone_mnt(mnt, dentry, flag);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!q)
|
|
|
|
goto Enomem;
|
|
|
|
q->mnt_mountpoint = mnt->mnt_mountpoint;
|
|
|
|
|
|
|
|
p = mnt;
|
2005-09-10 11:27:07 +04:00
|
|
|
list_for_each_entry(r, &mnt->mnt_mounts, mnt_child) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!lives_below_in_same_fs(r->mnt_mountpoint, dentry))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (s = r; s; s = next_mnt(s, r)) {
|
2005-11-08 01:21:20 +03:00
|
|
|
if (!(flag & CL_COPY_ALL) && IS_MNT_UNBINDABLE(s)) {
|
|
|
|
s = skip_mnt_tree(s);
|
|
|
|
continue;
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
while (p != s->mnt_parent) {
|
|
|
|
p = p->mnt_parent;
|
|
|
|
q = q->mnt_parent;
|
|
|
|
}
|
|
|
|
p = s;
|
2008-03-22 03:48:19 +03:00
|
|
|
path.mnt = q;
|
|
|
|
path.dentry = p->mnt_mountpoint;
|
2005-11-08 01:17:22 +03:00
|
|
|
q = clone_mnt(p, p->mnt_root, flag);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!q)
|
|
|
|
goto Enomem;
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
list_add_tail(&q->mnt_list, &res->mnt_list);
|
2008-03-22 03:48:19 +03:00
|
|
|
attach_mnt(q, &path);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
2005-11-08 01:16:09 +03:00
|
|
|
Enomem:
|
2005-04-17 02:20:36 +04:00
|
|
|
if (res) {
|
2005-11-08 01:17:04 +03:00
|
|
|
LIST_HEAD(umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock(&vfsmount_lock);
|
2005-11-08 01:20:17 +03:00
|
|
|
umount_tree(res, 0, &umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2005-11-08 01:17:04 +03:00
|
|
|
release_mounts(&umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2007-06-07 20:20:32 +04:00
|
|
|
struct vfsmount *collect_mounts(struct vfsmount *mnt, struct dentry *dentry)
|
|
|
|
{
|
|
|
|
struct vfsmount *tree;
|
|
|
|
down_read(&namespace_sem);
|
|
|
|
tree = copy_tree(mnt, dentry, CL_COPY_ALL | CL_PRIVATE);
|
|
|
|
up_read(&namespace_sem);
|
|
|
|
return tree;
|
|
|
|
}
|
|
|
|
|
|
|
|
void drop_collected_mounts(struct vfsmount *mnt)
|
|
|
|
{
|
|
|
|
LIST_HEAD(umount_list);
|
|
|
|
down_read(&namespace_sem);
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
umount_tree(mnt, 0, &umount_list);
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
up_read(&namespace_sem);
|
|
|
|
release_mounts(&umount_list);
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:19:50 +03:00
|
|
|
/*
|
|
|
|
* @source_mnt : mount tree to be attached
|
2005-11-08 01:20:03 +03:00
|
|
|
* @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)
|
2005-11-08 01:19:50 +03:00
|
|
|
*
|
|
|
|
* 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.
|
2005-11-08 01:21:20 +03:00
|
|
|
* ---------------------------------------------------------------------------
|
|
|
|
* | BIND MOUNT OPERATION |
|
|
|
|
* |**************************************************************************
|
|
|
|
* | source-->| shared | private | slave | unbindable |
|
|
|
|
* | dest | | | | |
|
|
|
|
* | | | | | | |
|
|
|
|
* | v | | | | |
|
|
|
|
* |**************************************************************************
|
|
|
|
* | shared | shared (++) | shared (+) | shared(+++)| invalid |
|
|
|
|
* | | | | | |
|
|
|
|
* |non-shared| shared (+) | private | slave (*) | invalid |
|
|
|
|
* ***************************************************************************
|
2005-11-08 01:19:50 +03:00
|
|
|
* 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.
|
2005-11-08 01:21:01 +03:00
|
|
|
* (+++) 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.
|
|
|
|
*
|
2005-11-08 01:21:20 +03:00
|
|
|
* ---------------------------------------------------------------------------
|
|
|
|
* | MOVE MOUNT OPERATION |
|
|
|
|
* |**************************************************************************
|
|
|
|
* | source-->| shared | private | slave | unbindable |
|
|
|
|
* | dest | | | | |
|
|
|
|
* | | | | | | |
|
|
|
|
* | v | | | | |
|
|
|
|
* |**************************************************************************
|
|
|
|
* | shared | shared (+) | shared (+) | shared(+++) | invalid |
|
|
|
|
* | | | | | |
|
|
|
|
* |non-shared| shared (+*) | private | slave (*) | unbindable |
|
|
|
|
* ***************************************************************************
|
2005-11-08 01:21:01 +03:00
|
|
|
*
|
|
|
|
* (+) the mount is moved to the destination. And is then propagated to
|
|
|
|
* all the mounts in the propagation tree of the destination mount.
|
2005-11-08 01:20:03 +03:00
|
|
|
* (+*) the mount is moved to the destination.
|
2005-11-08 01:21:01 +03:00
|
|
|
* (+++) 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.
|
2005-11-08 01:19:50 +03:00
|
|
|
*
|
|
|
|
* 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,
|
2008-03-22 03:48:19 +03:00
|
|
|
struct path *path, struct path *parent_path)
|
2005-11-08 01:19:50 +03:00
|
|
|
{
|
|
|
|
LIST_HEAD(tree_list);
|
2008-03-22 03:48:19 +03:00
|
|
|
struct vfsmount *dest_mnt = path->mnt;
|
|
|
|
struct dentry *dest_dentry = path->dentry;
|
2005-11-08 01:19:50 +03:00
|
|
|
struct vfsmount *child, *p;
|
|
|
|
|
|
|
|
if (propagate_mnt(dest_mnt, dest_dentry, source_mnt, &tree_list))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (IS_MNT_SHARED(dest_mnt)) {
|
|
|
|
for (p = source_mnt; p; p = next_mnt(p, source_mnt))
|
|
|
|
set_mnt_shared(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock(&vfsmount_lock);
|
2008-03-22 03:48:19 +03:00
|
|
|
if (parent_path) {
|
|
|
|
detach_mnt(source_mnt, parent_path);
|
|
|
|
attach_mnt(source_mnt, path);
|
2006-12-08 13:37:56 +03:00
|
|
|
touch_mnt_namespace(current->nsproxy->mnt_ns);
|
2005-11-08 01:20:03 +03:00
|
|
|
} else {
|
|
|
|
mnt_set_mountpoint(dest_mnt, dest_dentry, source_mnt);
|
|
|
|
commit_tree(source_mnt);
|
|
|
|
}
|
2005-11-08 01:19:50 +03:00
|
|
|
|
|
|
|
list_for_each_entry_safe(child, p, &tree_list, mnt_hash) {
|
|
|
|
list_del_init(&child->mnt_hash);
|
|
|
|
commit_tree(child);
|
|
|
|
}
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
static int graft_tree(struct vfsmount *mnt, struct nameidata *nd)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
if (mnt->mnt_sb->s_flags & MS_NOUSER)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (S_ISDIR(nd->path.dentry->d_inode->i_mode) !=
|
2005-04-17 02:20:36 +04:00
|
|
|
S_ISDIR(mnt->mnt_root->d_inode->i_mode))
|
|
|
|
return -ENOTDIR;
|
|
|
|
|
|
|
|
err = -ENOENT;
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_lock(&nd->path.dentry->d_inode->i_mutex);
|
|
|
|
if (IS_DEADDIR(nd->path.dentry->d_inode))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
err = security_sb_check_sb(mnt, nd);
|
|
|
|
if (err)
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
err = -ENOENT;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (IS_ROOT(nd->path.dentry) || !d_unhashed(nd->path.dentry))
|
2008-03-22 03:48:19 +03:00
|
|
|
err = attach_recursive_mnt(mnt, &nd->path, NULL);
|
2005-04-17 02:20:36 +04:00
|
|
|
out_unlock:
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_unlock(&nd->path.dentry->d_inode->i_mutex);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!err)
|
|
|
|
security_sb_post_addmount(mnt, nd);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:19:07 +03:00
|
|
|
/*
|
|
|
|
* recursively change the type of the mountpoint.
|
2008-02-08 15:22:12 +03:00
|
|
|
* noinline this do_mount helper to save do_mount stack space.
|
2005-11-08 01:19:07 +03:00
|
|
|
*/
|
2008-02-08 15:22:12 +03:00
|
|
|
static noinline int do_change_type(struct nameidata *nd, int flag)
|
2005-11-08 01:19:07 +03:00
|
|
|
{
|
2008-02-15 06:34:32 +03:00
|
|
|
struct vfsmount *m, *mnt = nd->path.mnt;
|
2005-11-08 01:19:07 +03:00
|
|
|
int recurse = flag & MS_REC;
|
|
|
|
int type = flag & ~MS_REC;
|
|
|
|
|
2007-05-08 11:30:40 +04:00
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (nd->path.dentry != nd->path.mnt->mnt_root)
|
2005-11-08 01:19:07 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
down_write(&namespace_sem);
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
|
|
|
|
change_mnt_propagation(m, type);
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
up_write(&namespace_sem);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
/*
|
|
|
|
* do loopback mount.
|
2008-02-08 15:22:12 +03:00
|
|
|
* noinline this do_mount helper to save do_mount stack space.
|
2005-04-17 02:20:36 +04:00
|
|
|
*/
|
2008-02-08 15:22:12 +03:00
|
|
|
static noinline int do_loopback(struct nameidata *nd, char *old_name,
|
|
|
|
int recurse)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct nameidata old_nd;
|
|
|
|
struct vfsmount *mnt = NULL;
|
|
|
|
int err = mount_is_safe(nd);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
if (!old_name || !*old_name)
|
|
|
|
return -EINVAL;
|
|
|
|
err = path_lookup(old_name, LOOKUP_FOLLOW, &old_nd);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
err = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (IS_MNT_UNBINDABLE(old_nd.path.mnt))
|
|
|
|
goto out;
|
2005-11-08 01:21:20 +03:00
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(nd->path.mnt) || !check_mnt(old_nd.path.mnt))
|
2005-11-08 01:15:04 +03:00
|
|
|
goto out;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:15:04 +03:00
|
|
|
err = -ENOMEM;
|
|
|
|
if (recurse)
|
2008-02-15 06:34:32 +03:00
|
|
|
mnt = copy_tree(old_nd.path.mnt, old_nd.path.dentry, 0);
|
2005-11-08 01:15:04 +03:00
|
|
|
else
|
2008-02-15 06:34:32 +03:00
|
|
|
mnt = clone_mnt(old_nd.path.mnt, old_nd.path.dentry, 0);
|
2005-11-08 01:15:04 +03:00
|
|
|
|
|
|
|
if (!mnt)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = graft_tree(mnt, nd);
|
|
|
|
if (err) {
|
2005-11-08 01:17:04 +03:00
|
|
|
LIST_HEAD(umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock(&vfsmount_lock);
|
2005-11-08 01:20:17 +03:00
|
|
|
umount_tree(mnt, 0, &umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2005-11-08 01:17:04 +03:00
|
|
|
release_mounts(&umount_list);
|
2005-11-08 01:16:29 +03:00
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:15:04 +03:00
|
|
|
out:
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&old_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
2008-02-08 15:22:12 +03:00
|
|
|
* noinline this do_mount helper to save do_mount stack space.
|
2005-04-17 02:20:36 +04:00
|
|
|
*/
|
2008-02-08 15:22:12 +03:00
|
|
|
static noinline int do_remount(struct nameidata *nd, int flags, int mnt_flags,
|
2005-04-17 02:20:36 +04:00
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
int err;
|
2008-02-15 06:34:32 +03:00
|
|
|
struct super_block *sb = nd->path.mnt->mnt_sb;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(nd->path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (nd->path.dentry != nd->path.mnt->mnt_root)
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
down_write(&sb->s_umount);
|
|
|
|
err = do_remount_sb(sb, flags, data, 0);
|
|
|
|
if (!err)
|
2008-02-15 06:34:32 +03:00
|
|
|
nd->path.mnt->mnt_flags = mnt_flags;
|
2005-04-17 02:20:36 +04:00
|
|
|
up_write(&sb->s_umount);
|
|
|
|
if (!err)
|
2008-02-15 06:34:32 +03:00
|
|
|
security_sb_post_remount(nd->path.mnt, flags, data);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:21:20 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-02-08 15:22:12 +03:00
|
|
|
/*
|
|
|
|
* noinline this do_mount helper to save do_mount stack space.
|
|
|
|
*/
|
|
|
|
static noinline int do_move_mount(struct nameidata *nd, char *old_name)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-03-22 03:48:19 +03:00
|
|
|
struct nameidata old_nd;
|
|
|
|
struct path parent_path;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct vfsmount *p;
|
|
|
|
int err = 0;
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
if (!old_name || !*old_name)
|
|
|
|
return -EINVAL;
|
|
|
|
err = path_lookup(old_name, LOOKUP_FOLLOW, &old_nd);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2008-02-15 06:34:32 +03:00
|
|
|
while (d_mountpoint(nd->path.dentry) &&
|
|
|
|
follow_down(&nd->path.mnt, &nd->path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
;
|
|
|
|
err = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(nd->path.mnt) || !check_mnt(old_nd.path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
err = -ENOENT;
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_lock(&nd->path.dentry->d_inode->i_mutex);
|
|
|
|
if (IS_DEADDIR(nd->path.dentry->d_inode))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out1;
|
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!IS_ROOT(nd->path.dentry) && d_unhashed(nd->path.dentry))
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
err = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (old_nd.path.dentry != old_nd.path.mnt->mnt_root)
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (old_nd.path.mnt == old_nd.path.mnt->mnt_parent)
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-02-15 06:34:32 +03:00
|
|
|
if (S_ISDIR(nd->path.dentry->d_inode->i_mode) !=
|
|
|
|
S_ISDIR(old_nd.path.dentry->d_inode->i_mode))
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
|
|
|
/*
|
|
|
|
* Don't move a mount residing in a shared parent.
|
|
|
|
*/
|
2008-02-15 06:34:32 +03:00
|
|
|
if (old_nd.path.mnt->mnt_parent &&
|
|
|
|
IS_MNT_SHARED(old_nd.path.mnt->mnt_parent))
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-11-08 01:21:20 +03:00
|
|
|
/*
|
|
|
|
* Don't move a mount tree containing unbindable mounts to a destination
|
|
|
|
* mount which is shared.
|
|
|
|
*/
|
2008-02-15 06:34:32 +03:00
|
|
|
if (IS_MNT_SHARED(nd->path.mnt) &&
|
|
|
|
tree_contains_unbindable(old_nd.path.mnt))
|
2005-11-08 01:21:20 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
err = -ELOOP;
|
2008-02-15 06:34:32 +03:00
|
|
|
for (p = nd->path.mnt; p->mnt_parent != p; p = p->mnt_parent)
|
|
|
|
if (p == old_nd.path.mnt)
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-03-22 03:48:19 +03:00
|
|
|
err = attach_recursive_mnt(old_nd.path.mnt, &nd->path, &parent_path);
|
2008-02-15 06:34:32 +03:00
|
|
|
if (err)
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out1;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* if the mount is moved, it should no longer be expire
|
|
|
|
* automatically */
|
2008-02-15 06:34:32 +03:00
|
|
|
list_del_init(&old_nd.path.mnt->mnt_expire);
|
2005-04-17 02:20:36 +04:00
|
|
|
out1:
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_unlock(&nd->path.dentry->d_inode->i_mutex);
|
2005-04-17 02:20:36 +04:00
|
|
|
out:
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!err)
|
2008-03-22 03:48:19 +03:00
|
|
|
path_put(&parent_path);
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&old_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* create a new mount for userspace and request it to be added into the
|
|
|
|
* namespace's tree
|
2008-02-08 15:22:12 +03:00
|
|
|
* noinline this do_mount helper to save do_mount stack space.
|
2005-04-17 02:20:36 +04:00
|
|
|
*/
|
2008-02-08 15:22:12 +03:00
|
|
|
static noinline int do_new_mount(struct nameidata *nd, char *type, int flags,
|
2005-04-17 02:20:36 +04:00
|
|
|
int mnt_flags, char *name, void *data)
|
|
|
|
{
|
|
|
|
struct vfsmount *mnt;
|
|
|
|
|
|
|
|
if (!type || !memchr(type, 0, PAGE_SIZE))
|
|
|
|
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, nd, 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 nameidata *nd,
|
|
|
|
int mnt_flags, struct list_head *fslist)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
/* Something was mounted here while we slept */
|
2008-02-15 06:34:32 +03:00
|
|
|
while (d_mountpoint(nd->path.dentry) &&
|
|
|
|
follow_down(&nd->path.mnt, &nd->path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
;
|
|
|
|
err = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(nd->path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
/* Refuse the same filesystem on the same mount point */
|
|
|
|
err = -EBUSY;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (nd->path.mnt->mnt_sb == newmnt->mnt_sb &&
|
|
|
|
nd->path.mnt->mnt_root == nd->path.dentry)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
err = -EINVAL;
|
|
|
|
if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
|
|
|
|
goto unlock;
|
|
|
|
|
|
|
|
newmnt->mnt_flags = mnt_flags;
|
2005-11-08 01:16:29 +03:00
|
|
|
if ((err = graft_tree(newmnt, nd)))
|
|
|
|
goto unlock;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2008-03-22 23:14:30 +03:00
|
|
|
if (fslist) /* add to the specified expiration list */
|
2005-07-08 04:57:30 +04:00
|
|
|
list_add_tail(&newmnt->mnt_expire, fslist);
|
2008-03-22 23:14:30 +03:00
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-11-08 01:16:29 +03:00
|
|
|
return 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
unlock:
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
mntput(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);
|
2008-03-22 07:21:53 +03:00
|
|
|
LIST_HEAD(umounts);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (list_empty(mounts))
|
|
|
|
return;
|
|
|
|
|
2008-03-22 07:21:53 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_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())
|
|
|
|
*/
|
2005-07-08 04:57:30 +04:00
|
|
|
list_for_each_entry_safe(mnt, next, mounts, mnt_expire) {
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!xchg(&mnt->mnt_expiry_mark, 1) ||
|
2008-03-22 07:21:53 +03:00
|
|
|
propagate_mount_busy(mnt, 1))
|
2005-04-17 02:20:36 +04:00
|
|
|
continue;
|
2005-07-08 04:57:30 +04:00
|
|
|
list_move(&mnt->mnt_expire, &graveyard);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2008-03-22 07:21:53 +03:00
|
|
|
while (!list_empty(&graveyard)) {
|
|
|
|
mnt = list_first_entry(&graveyard, struct vfsmount, mnt_expire);
|
|
|
|
touch_mnt_namespace(mnt->mnt_ns);
|
|
|
|
umount_tree(mnt, 1, &umounts);
|
|
|
|
}
|
2006-06-09 17:34:17 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2008-03-22 07:21:53 +03:00
|
|
|
up_write(&namespace_sem);
|
|
|
|
|
|
|
|
release_mounts(&umounts);
|
2006-06-09 17:34:17 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
2005-04-17 02:20:36 +04:00
|
|
|
continue;
|
2006-06-09 17:34:17 +04:00
|
|
|
/*
|
|
|
|
* Descend a level if the d_mounts list is non-empty.
|
|
|
|
*/
|
|
|
|
if (!list_empty(&mnt->mnt_mounts)) {
|
|
|
|
this_parent = mnt;
|
|
|
|
goto repeat;
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-06-09 17:34:17 +04:00
|
|
|
if (!propagate_mount_busy(mnt, 1)) {
|
|
|
|
list_move_tail(&mnt->mnt_expire, graveyard);
|
|
|
|
found++;
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2006-06-09 17:34:17 +04:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
2008-03-22 07:46:23 +03:00
|
|
|
static void shrink_submounts(struct vfsmount *mnt, struct list_head *umounts)
|
2006-06-09 17:34:17 +04:00
|
|
|
{
|
|
|
|
LIST_HEAD(graveyard);
|
2008-03-22 07:46:23 +03:00
|
|
|
struct vfsmount *m;
|
2006-06-09 17:34:17 +04:00
|
|
|
|
|
|
|
/* extract submounts of 'mountpoint' from the expiration list */
|
2008-03-22 07:46:23 +03:00
|
|
|
while (select_submounts(mnt, &graveyard)) {
|
2008-03-22 07:21:53 +03:00
|
|
|
while (!list_empty(&graveyard)) {
|
2008-03-22 07:46:23 +03:00
|
|
|
m = list_first_entry(&graveyard, struct vfsmount,
|
2008-03-22 07:21:53 +03:00
|
|
|
mnt_expire);
|
|
|
|
touch_mnt_namespace(mnt->mnt_ns);
|
2008-03-22 07:46:23 +03:00
|
|
|
umount_tree(mnt, 1, umounts);
|
2008-03-22 07:21:53 +03:00
|
|
|
}
|
|
|
|
}
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2005-11-08 01:16:09 +03:00
|
|
|
static long exact_copy_from_user(void *to, const void __user * from,
|
|
|
|
unsigned long n)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
int copy_mount_options(const void __user * data, unsigned long *where)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned long page;
|
|
|
|
unsigned long size;
|
2005-11-08 01:16:09 +03:00
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
*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) {
|
2005-11-08 01:16:09 +03:00
|
|
|
free_page(page);
|
2005-04-17 02:20:36 +04:00
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
if (i != PAGE_SIZE)
|
|
|
|
memset((char *)page + i, 0, PAGE_SIZE - i);
|
|
|
|
*where = page;
|
|
|
|
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.
|
|
|
|
*/
|
2005-11-08 01:16:09 +03:00
|
|
|
long do_mount(char *dev_name, char *dir_name, char *type_page,
|
2005-04-17 02:20:36 +04:00
|
|
|
unsigned long flags, void *data_page)
|
|
|
|
{
|
|
|
|
struct nameidata nd;
|
|
|
|
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 (dev_name && !memchr(dev_name, 0, PAGE_SIZE))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (data_page)
|
|
|
|
((char *)data_page)[PAGE_SIZE - 1] = 0;
|
|
|
|
|
|
|
|
/* 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;
|
2006-01-10 07:52:17 +03:00
|
|
|
if (flags & MS_NOATIME)
|
|
|
|
mnt_flags |= MNT_NOATIME;
|
|
|
|
if (flags & MS_NODIRATIME)
|
|
|
|
mnt_flags |= MNT_NODIRATIME;
|
2006-12-13 11:34:34 +03:00
|
|
|
if (flags & MS_RELATIME)
|
|
|
|
mnt_flags |= MNT_RELATIME;
|
2006-01-10 07:52:17 +03:00
|
|
|
|
|
|
|
flags &= ~(MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_ACTIVE |
|
2007-10-19 10:40:02 +04:00
|
|
|
MS_NOATIME | MS_NODIRATIME | MS_RELATIME| MS_KERNMOUNT);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
/* ... and get the mountpoint */
|
|
|
|
retval = path_lookup(dir_name, LOOKUP_FOLLOW, &nd);
|
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
retval = security_sb_mount(dev_name, &nd, type_page, flags, data_page);
|
|
|
|
if (retval)
|
|
|
|
goto dput_out;
|
|
|
|
|
|
|
|
if (flags & MS_REMOUNT)
|
|
|
|
retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
|
|
|
|
data_page);
|
|
|
|
else if (flags & MS_BIND)
|
[PATCH] revert "vfs: propagate mnt_flags into do_loopback/vfsmount"
Revert commit f6422f17d3a480f21917a3895e2a46b968f56a08, due to
Valdis.Kletnieks@vt.edu wrote:
>
> There seems to have been a bug introduced in this changeset:
>
> Am running 2.6.17-rc3-mm1. When this changeset is applied, 'mount --bind'
> misbehaves:
>
> > # mkdir /foo
> > # mount -t tmpfs -o rw,nosuid,nodev,noexec,noatime,nodiratime none /foo
> > # mkdir /foo/bar
> > # mount --bind /foo/bar /foo
> > # tail -2 /proc/mounts
> > none /foo tmpfs rw,nosuid,nodev,noexec,noatime,nodiratime 0 0
> > none /foo tmpfs rw 0 0
>
> Reverting this changeset causes both mounts to have the same options.
>
> (Thanks to Stephen Smalley for tracking down the changeset...)
>
Cc: Herbert Poetzl <herbert@13thfloor.at>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: <Valdis.Kletnieks@vt.edu>
Cc: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-05-15 20:44:30 +04:00
|
|
|
retval = do_loopback(&nd, dev_name, flags & MS_REC);
|
2005-11-08 01:21:20 +03:00
|
|
|
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
|
2005-11-08 01:19:07 +03:00
|
|
|
retval = do_change_type(&nd, flags);
|
2005-04-17 02:20:36 +04:00
|
|
|
else if (flags & MS_MOVE)
|
|
|
|
retval = do_move_mount(&nd, dev_name);
|
|
|
|
else
|
|
|
|
retval = do_new_mount(&nd, type_page, flags, mnt_flags,
|
|
|
|
dev_name, data_page);
|
|
|
|
dput_out:
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2006-02-07 23:59:00 +03:00
|
|
|
/*
|
|
|
|
* Allocate a new namespace structure and populate it with contents
|
|
|
|
* copied from the namespace of the passed in task structure.
|
|
|
|
*/
|
2007-05-08 11:25:21 +04:00
|
|
|
static struct mnt_namespace *dup_mnt_ns(struct mnt_namespace *mnt_ns,
|
2006-12-08 13:37:56 +03:00
|
|
|
struct fs_struct *fs)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *new_ns;
|
2005-04-17 02:20:36 +04:00
|
|
|
struct vfsmount *rootmnt = NULL, *pwdmnt = NULL, *altrootmnt = NULL;
|
|
|
|
struct vfsmount *p, *q;
|
|
|
|
|
2006-12-08 13:37:56 +03:00
|
|
|
new_ns = kmalloc(sizeof(struct mnt_namespace), GFP_KERNEL);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!new_ns)
|
2007-07-16 10:41:06 +04:00
|
|
|
return ERR_PTR(-ENOMEM);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
atomic_set(&new_ns->count, 1);
|
|
|
|
INIT_LIST_HEAD(&new_ns->list);
|
2005-11-08 01:15:49 +03:00
|
|
|
init_waitqueue_head(&new_ns->poll);
|
|
|
|
new_ns->event = 0;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
/* First pass: copy the tree topology */
|
2006-12-08 13:37:56 +03:00
|
|
|
new_ns->root = copy_tree(mnt_ns->root, mnt_ns->root->mnt_root,
|
2005-11-08 01:21:20 +03:00
|
|
|
CL_COPY_ALL | CL_EXPIRE);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (!new_ns->root) {
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
kfree(new_ns);
|
2007-07-16 10:41:06 +04:00
|
|
|
return ERR_PTR(-ENOMEM);;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
spin_lock(&vfsmount_lock);
|
|
|
|
list_add_tail(&new_ns->list, &new_ns->root->mnt_list);
|
|
|
|
spin_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.
|
|
|
|
*/
|
2006-12-08 13:37:56 +03:00
|
|
|
p = mnt_ns->root;
|
2005-04-17 02:20:36 +04:00
|
|
|
q = new_ns->root;
|
|
|
|
while (p) {
|
2006-12-08 13:37:56 +03:00
|
|
|
q->mnt_ns = new_ns;
|
2005-04-17 02:20:36 +04:00
|
|
|
if (fs) {
|
2008-02-15 06:34:38 +03:00
|
|
|
if (p == fs->root.mnt) {
|
2005-04-17 02:20:36 +04:00
|
|
|
rootmnt = p;
|
2008-02-15 06:34:38 +03:00
|
|
|
fs->root.mnt = mntget(q);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2008-02-15 06:34:38 +03:00
|
|
|
if (p == fs->pwd.mnt) {
|
2005-04-17 02:20:36 +04:00
|
|
|
pwdmnt = p;
|
2008-02-15 06:34:38 +03:00
|
|
|
fs->pwd.mnt = mntget(q);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
2008-02-15 06:34:38 +03:00
|
|
|
if (p == fs->altroot.mnt) {
|
2005-04-17 02:20:36 +04:00
|
|
|
altrootmnt = p;
|
2008-02-15 06:34:38 +03:00
|
|
|
fs->altroot.mnt = mntget(q);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
}
|
2006-12-08 13:37:56 +03:00
|
|
|
p = next_mnt(p, mnt_ns->root);
|
2005-04-17 02:20:36 +04:00
|
|
|
q = next_mnt(q, new_ns->root);
|
|
|
|
}
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (rootmnt)
|
|
|
|
mntput(rootmnt);
|
|
|
|
if (pwdmnt)
|
|
|
|
mntput(pwdmnt);
|
|
|
|
if (altrootmnt)
|
|
|
|
mntput(altrootmnt);
|
|
|
|
|
2006-02-07 23:59:00 +03:00
|
|
|
return new_ns;
|
|
|
|
}
|
|
|
|
|
2007-07-16 10:41:15 +04:00
|
|
|
struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
|
2007-05-08 11:25:21 +04:00
|
|
|
struct fs_struct *new_fs)
|
2006-02-07 23:59:00 +03:00
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *new_ns;
|
2006-02-07 23:59:00 +03:00
|
|
|
|
2007-05-08 11:25:21 +04:00
|
|
|
BUG_ON(!ns);
|
2006-12-08 13:37:56 +03:00
|
|
|
get_mnt_ns(ns);
|
2006-02-07 23:59:00 +03:00
|
|
|
|
|
|
|
if (!(flags & CLONE_NEWNS))
|
2007-05-08 11:25:21 +04:00
|
|
|
return ns;
|
2006-02-07 23:59:00 +03:00
|
|
|
|
2007-05-08 11:25:21 +04:00
|
|
|
new_ns = dup_mnt_ns(ns, new_fs);
|
2006-02-07 23:59:00 +03:00
|
|
|
|
2006-12-08 13:37:56 +03:00
|
|
|
put_mnt_ns(ns);
|
2007-05-08 11:25:21 +04:00
|
|
|
return new_ns;
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
asmlinkage long sys_mount(char __user * dev_name, char __user * dir_name,
|
|
|
|
char __user * type, unsigned long flags,
|
|
|
|
void __user * data)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
unsigned long data_page;
|
|
|
|
unsigned long type_page;
|
|
|
|
unsigned long dev_page;
|
|
|
|
char *dir_page;
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
retval = copy_mount_options(type, &type_page);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (retval < 0)
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
dir_page = getname(dir_name);
|
|
|
|
retval = PTR_ERR(dir_page);
|
|
|
|
if (IS_ERR(dir_page))
|
|
|
|
goto out1;
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
retval = copy_mount_options(dev_name, &dev_page);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (retval < 0)
|
|
|
|
goto out2;
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
retval = copy_mount_options(data, &data_page);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (retval < 0)
|
|
|
|
goto out3;
|
|
|
|
|
|
|
|
lock_kernel();
|
2005-11-08 01:16:09 +03:00
|
|
|
retval = do_mount((char *)dev_page, dir_page, (char *)type_page,
|
|
|
|
flags, (void *)data_page);
|
2005-04-17 02:20:36 +04:00
|
|
|
unlock_kernel();
|
|
|
|
free_page(data_page);
|
|
|
|
|
|
|
|
out3:
|
|
|
|
free_page(dev_page);
|
|
|
|
out2:
|
|
|
|
putname(dir_page);
|
|
|
|
out1:
|
|
|
|
free_page(type_page);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values.
|
|
|
|
* It can block. Requires the big lock held.
|
|
|
|
*/
|
2008-02-15 06:34:39 +03:00
|
|
|
void set_fs_root(struct fs_struct *fs, struct path *path)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-02-15 06:34:38 +03:00
|
|
|
struct path old_root;
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
write_lock(&fs->lock);
|
|
|
|
old_root = fs->root;
|
2008-02-15 06:34:39 +03:00
|
|
|
fs->root = *path;
|
|
|
|
path_get(path);
|
2005-04-17 02:20:36 +04:00
|
|
|
write_unlock(&fs->lock);
|
2008-02-15 06:34:38 +03:00
|
|
|
if (old_root.dentry)
|
|
|
|
path_put(&old_root);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Replace the fs->{pwdmnt,pwd} with {mnt,dentry}. Put the old values.
|
|
|
|
* It can block. Requires the big lock held.
|
|
|
|
*/
|
2008-02-15 06:34:39 +03:00
|
|
|
void set_fs_pwd(struct fs_struct *fs, struct path *path)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-02-15 06:34:38 +03:00
|
|
|
struct path old_pwd;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
write_lock(&fs->lock);
|
|
|
|
old_pwd = fs->pwd;
|
2008-02-15 06:34:39 +03:00
|
|
|
fs->pwd = *path;
|
|
|
|
path_get(path);
|
2005-04-17 02:20:36 +04:00
|
|
|
write_unlock(&fs->lock);
|
|
|
|
|
2008-02-15 06:34:38 +03:00
|
|
|
if (old_pwd.dentry)
|
|
|
|
path_put(&old_pwd);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2008-03-22 03:48:19 +03:00
|
|
|
static void chroot_fs_refs(struct path *old_root, struct path *new_root)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct task_struct *g, *p;
|
|
|
|
struct fs_struct *fs;
|
|
|
|
|
|
|
|
read_lock(&tasklist_lock);
|
|
|
|
do_each_thread(g, p) {
|
|
|
|
task_lock(p);
|
|
|
|
fs = p->fs;
|
|
|
|
if (fs) {
|
|
|
|
atomic_inc(&fs->count);
|
|
|
|
task_unlock(p);
|
2008-03-22 03:48:19 +03:00
|
|
|
if (fs->root.dentry == old_root->dentry
|
|
|
|
&& fs->root.mnt == old_root->mnt)
|
|
|
|
set_fs_root(fs, new_root);
|
|
|
|
if (fs->pwd.dentry == old_root->dentry
|
|
|
|
&& fs->pwd.mnt == old_root->mnt)
|
|
|
|
set_fs_pwd(fs, new_root);
|
2005-04-17 02:20:36 +04:00
|
|
|
put_fs_struct(fs);
|
|
|
|
} else
|
|
|
|
task_unlock(p);
|
|
|
|
} while_each_thread(g, p);
|
|
|
|
read_unlock(&tasklist_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2006-01-08 12:03:18 +03:00
|
|
|
* Also, the current root cannot be on the 'rootfs' (initial ramfs) filesystem.
|
|
|
|
* See Documentation/filesystems/ramfs-rootfs-initramfs.txt for alternatives
|
|
|
|
* in this situation.
|
|
|
|
*
|
2005-04-17 02:20:36 +04:00
|
|
|
* 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.
|
|
|
|
*/
|
2005-11-08 01:16:09 +03:00
|
|
|
asmlinkage long sys_pivot_root(const char __user * new_root,
|
|
|
|
const char __user * put_old)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
|
|
|
struct vfsmount *tmp;
|
2008-03-22 03:48:19 +03:00
|
|
|
struct nameidata new_nd, old_nd, user_nd;
|
|
|
|
struct path parent_path, root_parent;
|
2005-04-17 02:20:36 +04:00
|
|
|
int error;
|
|
|
|
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
lock_kernel();
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
error = __user_walk(new_root, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
|
|
|
|
&new_nd);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (error)
|
|
|
|
goto out0;
|
|
|
|
error = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(new_nd.path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out1;
|
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
error = __user_walk(put_old, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &old_nd);
|
2005-04-17 02:20:36 +04:00
|
|
|
if (error)
|
|
|
|
goto out1;
|
|
|
|
|
|
|
|
error = security_sb_pivotroot(&old_nd, &new_nd);
|
|
|
|
if (error) {
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&old_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out1;
|
|
|
|
}
|
|
|
|
|
|
|
|
read_lock(¤t->fs->lock);
|
2008-02-15 06:34:38 +03:00
|
|
|
user_nd.path = current->fs->root;
|
|
|
|
path_get(¤t->fs->root);
|
2005-04-17 02:20:36 +04:00
|
|
|
read_unlock(¤t->fs->lock);
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_lock(&old_nd.path.dentry->d_inode->i_mutex);
|
2005-04-17 02:20:36 +04:00
|
|
|
error = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (IS_MNT_SHARED(old_nd.path.mnt) ||
|
|
|
|
IS_MNT_SHARED(new_nd.path.mnt->mnt_parent) ||
|
|
|
|
IS_MNT_SHARED(user_nd.path.mnt->mnt_parent))
|
2005-11-08 01:20:03 +03:00
|
|
|
goto out2;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!check_mnt(user_nd.path.mnt))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2;
|
|
|
|
error = -ENOENT;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (IS_DEADDIR(new_nd.path.dentry->d_inode))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (d_unhashed(new_nd.path.dentry) && !IS_ROOT(new_nd.path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (d_unhashed(old_nd.path.dentry) && !IS_ROOT(old_nd.path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2;
|
|
|
|
error = -EBUSY;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (new_nd.path.mnt == user_nd.path.mnt ||
|
|
|
|
old_nd.path.mnt == user_nd.path.mnt)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2; /* loop, on the same file system */
|
|
|
|
error = -EINVAL;
|
2008-02-15 06:34:32 +03:00
|
|
|
if (user_nd.path.mnt->mnt_root != user_nd.path.dentry)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2; /* not a mountpoint */
|
2008-02-15 06:34:32 +03:00
|
|
|
if (user_nd.path.mnt->mnt_parent == user_nd.path.mnt)
|
2005-09-07 02:19:36 +04:00
|
|
|
goto out2; /* not attached */
|
2008-02-15 06:34:32 +03:00
|
|
|
if (new_nd.path.mnt->mnt_root != new_nd.path.dentry)
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out2; /* not a mountpoint */
|
2008-02-15 06:34:32 +03:00
|
|
|
if (new_nd.path.mnt->mnt_parent == new_nd.path.mnt)
|
2005-09-07 02:19:36 +04:00
|
|
|
goto out2; /* not attached */
|
2008-02-15 06:34:32 +03:00
|
|
|
/* make sure we can reach put_old from new_root */
|
|
|
|
tmp = old_nd.path.mnt;
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock(&vfsmount_lock);
|
2008-02-15 06:34:32 +03:00
|
|
|
if (tmp != new_nd.path.mnt) {
|
2005-04-17 02:20:36 +04:00
|
|
|
for (;;) {
|
|
|
|
if (tmp->mnt_parent == tmp)
|
|
|
|
goto out3; /* already mounted on put_old */
|
2008-02-15 06:34:32 +03:00
|
|
|
if (tmp->mnt_parent == new_nd.path.mnt)
|
2005-04-17 02:20:36 +04:00
|
|
|
break;
|
|
|
|
tmp = tmp->mnt_parent;
|
|
|
|
}
|
2008-02-15 06:34:32 +03:00
|
|
|
if (!is_subdir(tmp->mnt_mountpoint, new_nd.path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out3;
|
2008-02-15 06:34:32 +03:00
|
|
|
} else if (!is_subdir(old_nd.path.dentry, new_nd.path.dentry))
|
2005-04-17 02:20:36 +04:00
|
|
|
goto out3;
|
2008-03-22 03:48:19 +03:00
|
|
|
detach_mnt(new_nd.path.mnt, &parent_path);
|
2008-02-15 06:34:32 +03:00
|
|
|
detach_mnt(user_nd.path.mnt, &root_parent);
|
|
|
|
/* mount old root on put_old */
|
2008-03-22 03:48:19 +03:00
|
|
|
attach_mnt(user_nd.path.mnt, &old_nd.path);
|
2008-02-15 06:34:32 +03:00
|
|
|
/* mount new_root on / */
|
|
|
|
attach_mnt(new_nd.path.mnt, &root_parent);
|
2006-12-08 13:37:56 +03:00
|
|
|
touch_mnt_namespace(current->nsproxy->mnt_ns);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2008-03-22 03:48:19 +03:00
|
|
|
chroot_fs_refs(&user_nd.path, &new_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
security_sb_post_pivotroot(&user_nd, &new_nd);
|
|
|
|
error = 0;
|
2008-03-22 03:48:19 +03:00
|
|
|
path_put(&root_parent);
|
|
|
|
path_put(&parent_path);
|
2005-04-17 02:20:36 +04:00
|
|
|
out2:
|
2008-02-15 06:34:32 +03:00
|
|
|
mutex_unlock(&old_nd.path.dentry->d_inode->i_mutex);
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&user_nd.path);
|
|
|
|
path_put(&old_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
out1:
|
2008-02-15 06:34:35 +03:00
|
|
|
path_put(&new_nd.path);
|
2005-04-17 02:20:36 +04:00
|
|
|
out0:
|
|
|
|
unlock_kernel();
|
|
|
|
return error;
|
|
|
|
out3:
|
|
|
|
spin_unlock(&vfsmount_lock);
|
|
|
|
goto out2;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __init init_mount_tree(void)
|
|
|
|
{
|
|
|
|
struct vfsmount *mnt;
|
2006-12-08 13:37:56 +03:00
|
|
|
struct mnt_namespace *ns;
|
2008-02-15 06:34:39 +03:00
|
|
|
struct path root;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
|
|
|
|
if (IS_ERR(mnt))
|
|
|
|
panic("Can't create rootfs");
|
2006-12-08 13:37:56 +03:00
|
|
|
ns = kmalloc(sizeof(*ns), GFP_KERNEL);
|
|
|
|
if (!ns)
|
2005-04-17 02:20:36 +04:00
|
|
|
panic("Can't allocate initial namespace");
|
2006-12-08 13:37:56 +03:00
|
|
|
atomic_set(&ns->count, 1);
|
|
|
|
INIT_LIST_HEAD(&ns->list);
|
|
|
|
init_waitqueue_head(&ns->poll);
|
|
|
|
ns->event = 0;
|
|
|
|
list_add(&mnt->mnt_list, &ns->list);
|
|
|
|
ns->root = mnt;
|
|
|
|
mnt->mnt_ns = ns;
|
|
|
|
|
|
|
|
init_task.nsproxy->mnt_ns = ns;
|
|
|
|
get_mnt_ns(ns);
|
|
|
|
|
2008-02-15 06:34:39 +03:00
|
|
|
root.mnt = ns->root;
|
|
|
|
root.dentry = ns->root->mnt_root;
|
|
|
|
|
|
|
|
set_fs_pwd(current->fs, &root);
|
|
|
|
set_fs_root(current->fs, &root);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|
|
|
|
|
2007-10-17 10:26:30 +04:00
|
|
|
void __init mnt_init(void)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2008-02-06 12:37:57 +03:00
|
|
|
unsigned u;
|
2006-09-29 12:58:57 +04:00
|
|
|
int err;
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:17:51 +03:00
|
|
|
init_rwsem(&namespace_sem);
|
|
|
|
|
2005-04-17 02:20:36 +04:00
|
|
|
mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
|
2007-07-20 05:11:58 +04:00
|
|
|
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2005-11-08 01:16:09 +03:00
|
|
|
mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
|
|
|
if (!mount_hashtable)
|
|
|
|
panic("Failed to allocate mount hash table\n");
|
|
|
|
|
2008-02-06 12:37:57 +03:00
|
|
|
printk("Mount-cache hash table entries: %lu\n", HASH_SIZE);
|
|
|
|
|
|
|
|
for (u = 0; u < HASH_SIZE; u++)
|
|
|
|
INIT_LIST_HEAD(&mount_hashtable[u]);
|
2005-04-17 02:20:36 +04:00
|
|
|
|
2006-09-29 12:58:57 +04:00
|
|
|
err = sysfs_init();
|
|
|
|
if (err)
|
|
|
|
printk(KERN_WARNING "%s: sysfs_init error: %d\n",
|
|
|
|
__FUNCTION__, err);
|
2007-10-29 23:17:23 +03:00
|
|
|
fs_kobj = kobject_create_and_add("fs", NULL);
|
|
|
|
if (!fs_kobj)
|
|
|
|
printk(KERN_WARNING "%s: kobj create error\n", __FUNCTION__);
|
2005-04-17 02:20:36 +04:00
|
|
|
init_rootfs();
|
|
|
|
init_mount_tree();
|
|
|
|
}
|
|
|
|
|
2006-12-08 13:37:56 +03:00
|
|
|
void __put_mnt_ns(struct mnt_namespace *ns)
|
2005-04-17 02:20:36 +04:00
|
|
|
{
|
2006-12-08 13:37:56 +03:00
|
|
|
struct vfsmount *root = ns->root;
|
2005-11-08 01:17:04 +03:00
|
|
|
LIST_HEAD(umount_list);
|
2006-12-08 13:37:56 +03:00
|
|
|
ns->root = NULL;
|
2005-07-08 04:57:24 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2005-11-08 01:17:51 +03:00
|
|
|
down_write(&namespace_sem);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_lock(&vfsmount_lock);
|
2005-11-08 01:20:17 +03:00
|
|
|
umount_tree(root, 0, &umount_list);
|
2005-04-17 02:20:36 +04:00
|
|
|
spin_unlock(&vfsmount_lock);
|
2005-11-08 01:17:51 +03:00
|
|
|
up_write(&namespace_sem);
|
2005-11-08 01:17:04 +03:00
|
|
|
release_mounts(&umount_list);
|
2006-12-08 13:37:56 +03:00
|
|
|
kfree(ns);
|
2005-04-17 02:20:36 +04:00
|
|
|
}
|