Skip to content
Snippets Groups Projects
namespace.c 57.7 KiB
Newer Older
		retval = do_change_type(&path, flags);
Linus Torvalds's avatar
Linus Torvalds committed
	else if (flags & MS_MOVE)
		retval = do_move_mount(&path, dev_name);
Linus Torvalds's avatar
Linus Torvalds committed
	else
		retval = do_new_mount(&path, type_page, flags, mnt_flags,
Linus Torvalds's avatar
Linus Torvalds committed
				      dev_name, data_page);
dput_out:
Linus Torvalds's avatar
Linus Torvalds committed
	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,
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct mnt_namespace *new_ns;
Al Viro's avatar
Al Viro committed
	struct vfsmount *rootmnt = NULL, *pwdmnt = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
	struct vfsmount *p, *q;

	new_ns = alloc_mnt_ns();
	if (IS_ERR(new_ns))
		return new_ns;
Linus Torvalds's avatar
Linus Torvalds committed

	down_write(&namespace_sem);
Linus Torvalds's avatar
Linus Torvalds committed
	/* First pass: copy the tree topology */
	new_ns->root = copy_tree(mnt_ns->root, mnt_ns->root->mnt_root,
Ram Pai's avatar
Ram Pai committed
					CL_COPY_ALL | CL_EXPIRE);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!new_ns->root) {
		up_write(&namespace_sem);
Linus Torvalds's avatar
Linus Torvalds committed
		kfree(new_ns);
		return ERR_PTR(-ENOMEM);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	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.
	 */
Linus Torvalds's avatar
Linus Torvalds committed
	q = new_ns->root;
	while (p) {
Linus Torvalds's avatar
Linus Torvalds committed
		if (fs) {
Jan Blunck's avatar
Jan Blunck committed
			if (p == fs->root.mnt) {
Linus Torvalds's avatar
Linus Torvalds committed
				rootmnt = p;
Jan Blunck's avatar
Jan Blunck committed
				fs->root.mnt = mntget(q);
Linus Torvalds's avatar
Linus Torvalds committed
			}
Jan Blunck's avatar
Jan Blunck committed
			if (p == fs->pwd.mnt) {
Linus Torvalds's avatar
Linus Torvalds committed
				pwdmnt = p;
Jan Blunck's avatar
Jan Blunck committed
				fs->pwd.mnt = mntget(q);
Linus Torvalds's avatar
Linus Torvalds committed
			}
		}
		p = next_mnt(p, mnt_ns->root);
Linus Torvalds's avatar
Linus Torvalds committed
		q = next_mnt(q, new_ns->root);
	}
	up_write(&namespace_sem);
Linus Torvalds's avatar
Linus Torvalds committed

	if (rootmnt)
		mntput(rootmnt);
	if (pwdmnt)
		mntput(pwdmnt);

struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
	struct mnt_namespace *new_ns;

	if (!(flags & CLONE_NEWNS))
	new_ns = dup_mnt_ns(ns, new_fs);
/**
 * 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;
}
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
		char __user *, type, unsigned long, flags, void __user *, data)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int ret;
	char *kernel_type;
	char *kernel_dir;
	char *kernel_dev;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned long data_page;

	ret = copy_mount_string(type, &kernel_type);
	if (ret < 0)
		goto out_type;
Linus Torvalds's avatar
Linus Torvalds committed

	kernel_dir = getname(dir_name);
	if (IS_ERR(kernel_dir)) {
		ret = PTR_ERR(kernel_dir);
		goto out_dir;
	}
Linus Torvalds's avatar
Linus Torvalds committed

	ret = copy_mount_string(dev_name, &kernel_dev);
	if (ret < 0)
		goto out_dev;
Linus Torvalds's avatar
Linus Torvalds committed

	ret = copy_mount_options(data, &data_page);
	if (ret < 0)
		goto out_data;
Linus Torvalds's avatar
Linus Torvalds committed

	ret = do_mount(kernel_dev, kernel_dir, kernel_type, flags,
		(void *) data_page);
Linus Torvalds's avatar
Linus Torvalds committed

	free_page(data_page);
out_data:
	kfree(kernel_dev);
out_dev:
	putname(kernel_dir);
out_dir:
	kfree(kernel_type);
out_type:
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 * 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.
 *
Linus Torvalds's avatar
Linus Torvalds committed
 * 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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct vfsmount *tmp;
	struct path new, old, parent_path, root_parent, root;
Linus Torvalds's avatar
Linus Torvalds committed
	int error;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	error = user_path_dir(new_root, &new);
Linus Torvalds's avatar
Linus Torvalds committed
	if (error)
		goto out0;
	error = -EINVAL;
	if (!check_mnt(new.mnt))
Linus Torvalds's avatar
Linus Torvalds committed
		goto out1;

	error = user_path_dir(put_old, &old);
Linus Torvalds's avatar
Linus Torvalds committed
	if (error)
		goto out1;

	error = security_sb_pivotroot(&old, &new);
Linus Torvalds's avatar
Linus Torvalds committed
	if (error) {
		path_put(&old);
Linus Torvalds's avatar
Linus Torvalds committed
		goto out1;
	}

	get_fs_root(current->fs, &root);
	down_write(&namespace_sem);
	mutex_lock(&old.dentry->d_inode->i_mutex);
Linus Torvalds's avatar
Linus Torvalds committed
	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))
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2;
	error = -ENOENT;
	if (cant_mount(old.dentry))
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2;
	if (d_unlinked(new.dentry))
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2;
	if (d_unlinked(old.dentry))
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2;
	error = -EBUSY;
	if (new.mnt == root.mnt ||
	    old.mnt == root.mnt)
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2; /* loop, on the same file system  */
	error = -EINVAL;
	if (root.mnt->mnt_root != root.dentry)
Linus Torvalds's avatar
Linus Torvalds committed
		goto out2; /* not a mountpoint */
	if (root.mnt->mnt_parent == root.mnt)
		goto out2; /* not attached */
	if (new.mnt->mnt_root != new.dentry)
Linus Torvalds's avatar
Linus Torvalds committed
		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;
Linus Torvalds's avatar
Linus Torvalds committed
	spin_lock(&vfsmount_lock);
	if (tmp != new.mnt) {
Linus Torvalds's avatar
Linus Torvalds committed
		for (;;) {
			if (tmp->mnt_parent == tmp)
				goto out3; /* already mounted on put_old */
			if (tmp->mnt_parent == new.mnt)
Linus Torvalds's avatar
Linus Torvalds committed
				break;
			tmp = tmp->mnt_parent;
		}
		if (!is_subdir(tmp->mnt_mountpoint, new.dentry))
Linus Torvalds's avatar
Linus Torvalds committed
			goto out3;
	} else if (!is_subdir(old.dentry, new.dentry))
Linus Torvalds's avatar
Linus Torvalds committed
		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);
	attach_mnt(new.mnt, &root_parent);
	touch_mnt_namespace(current->nsproxy->mnt_ns);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_unlock(&vfsmount_lock);
	chroot_fs_refs(&root, &new);
Linus Torvalds's avatar
Linus Torvalds committed
	error = 0;
	path_put(&root_parent);
	path_put(&parent_path);
Linus Torvalds's avatar
Linus Torvalds committed
out2:
	mutex_unlock(&old.dentry->d_inode->i_mutex);
	up_write(&namespace_sem);
	path_put(&old);
Linus Torvalds's avatar
Linus Torvalds committed
out1:
	path_put(&new);
Linus Torvalds's avatar
Linus Torvalds committed
out0:
	return error;
out3:
	spin_unlock(&vfsmount_lock);
	goto out2;
}

static void __init init_mount_tree(void)
{
	struct vfsmount *mnt;
	struct mnt_namespace *ns;
	struct path root;
Linus Torvalds's avatar
Linus Torvalds committed

	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))
Linus Torvalds's avatar
Linus Torvalds committed
		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)
Linus Torvalds's avatar
Linus Torvalds committed
{
	unsigned u;
Linus Torvalds's avatar
Linus Torvalds committed

	init_rwsem(&namespace_sem);

Linus Torvalds's avatar
Linus Torvalds committed
	mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
			0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
Linus Torvalds's avatar
Linus Torvalds committed

Ram Pai's avatar
Ram Pai committed
	mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
Linus Torvalds's avatar
Linus Torvalds committed

	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]);
Linus Torvalds's avatar
Linus Torvalds committed

	err = sysfs_init();
	if (err)
		printk(KERN_WARNING "%s: sysfs_init error: %d\n",
	fs_kobj = kobject_create_and_add("fs", NULL);
	if (!fs_kobj)
		printk(KERN_WARNING "%s: kobj create error\n", __func__);
Linus Torvalds's avatar
Linus Torvalds committed
	init_rootfs();
	init_mount_tree();
}

void put_mnt_ns(struct mnt_namespace *ns)
Linus Torvalds's avatar
Linus Torvalds committed
{
	LIST_HEAD(umount_list);
	if (!atomic_dec_and_test(&ns->count))
	down_write(&namespace_sem);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_lock(&vfsmount_lock);
	umount_tree(ns->root, 0, &umount_list);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_unlock(&vfsmount_lock);
	up_write(&namespace_sem);
	release_mounts(&umount_list);
Linus Torvalds's avatar
Linus Torvalds committed
}