Newer
Older
/*
* 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>
#include <linux/kernel.h>
#include <linux/cpumask.h>
#include <linux/mnt_namespace.h>
#include <linux/security.h>
#include <linux/mount.h>

David Howells
committed
#include <linux/ramfs.h>
#define HASH_SHIFT ilog2(PAGE_SIZE / sizeof(struct list_head))
#define HASH_SIZE (1UL << HASH_SHIFT)
/* spinlock for vfsmount related operations, inplace of dcache_lock */
__cacheline_aligned_in_smp DEFINE_SPINLOCK(vfsmount_lock);
static int event;
static DEFINE_IDA(mnt_group_ida);
static int mnt_id_start = 0;
static int mnt_group_start = 1;
static struct list_head *mount_hashtable __read_mostly;
static struct kmem_cache *mnt_cache __read_mostly;
struct kobject *fs_kobj;
EXPORT_SYMBOL_GPL(fs_kobj);
static inline unsigned long hash(struct vfsmount *mnt, struct dentry *dentry)
{
unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES);
tmp += ((unsigned long)dentry / L1_CACHE_BYTES);
tmp = tmp + (tmp >> HASH_SHIFT);
return tmp & (HASH_SIZE - 1);
#define MNT_WRITER_UNDERFLOW_LIMIT -(1<<16)
/* allocation is serialized by namespace_sem */
static int mnt_alloc_id(struct vfsmount *mnt)
{
int res;
retry:
ida_pre_get(&mnt_id_ida, GFP_KERNEL);
spin_lock(&vfsmount_lock);
res = ida_get_new_above(&mnt_id_ida, mnt_id_start, &mnt->mnt_id);
if (!res)
mnt_id_start = mnt->mnt_id + 1;
spin_unlock(&vfsmount_lock);
if (res == -EAGAIN)
goto retry;
return res;
}
static void mnt_free_id(struct vfsmount *mnt)
{
ida_remove(&mnt_id_ida, id);
if (mnt_id_start > id)
mnt_id_start = id;
spin_unlock(&vfsmount_lock);
}
/*
* Allocate a new peer group ID
*
* mnt_group_ida is protected by namespace_sem
*/
static int mnt_alloc_group_id(struct vfsmount *mnt)
{
if (!ida_pre_get(&mnt_group_ida, GFP_KERNEL))
return -ENOMEM;
res = ida_get_new_above(&mnt_group_ida,
mnt_group_start,
&mnt->mnt_group_id);
if (!res)
mnt_group_start = mnt->mnt_group_id + 1;
return res;
}
/*
* Release a peer group ID
*/
void mnt_release_group_id(struct vfsmount *mnt)
{
int id = mnt->mnt_group_id;
ida_remove(&mnt_group_ida, id);
if (mnt_group_start > id)
mnt_group_start = id;
mnt->mnt_group_id = 0;
}
struct vfsmount *mnt = kmem_cache_zalloc(mnt_cache, GFP_KERNEL);
int err;
err = mnt_alloc_id(mnt);
if (err)
goto out_free_cache;
if (name) {
mnt->mnt_devname = kstrdup(name, GFP_KERNEL);
if (!mnt->mnt_devname)
goto out_free_id;
INIT_LIST_HEAD(&mnt->mnt_hash);
INIT_LIST_HEAD(&mnt->mnt_child);
INIT_LIST_HEAD(&mnt->mnt_mounts);
INIT_LIST_HEAD(&mnt->mnt_list);
INIT_LIST_HEAD(&mnt->mnt_expire);
INIT_LIST_HEAD(&mnt->mnt_slave_list);
INIT_LIST_HEAD(&mnt->mnt_slave);
#ifdef CONFIG_SMP
mnt->mnt_writers = alloc_percpu(int);
if (!mnt->mnt_writers)
goto out_free_devname;
#else
mnt->mnt_writers = 0;
#endif
#ifdef CONFIG_SMP
out_free_devname:
kfree(mnt->mnt_devname);
#endif
out_free_id:
mnt_free_id(mnt);
out_free_cache:
kmem_cache_free(mnt_cache, mnt);
return NULL;
/*
* Most r/o checks on a fs are for operations that take
* discrete amounts of time, like a write() or unlink().
* We must keep track of when those operations start
* (for permission checks) and when they end, so that
* we can determine when writes are able to occur to
* a filesystem.
*/
/*
* __mnt_is_readonly: check whether a mount is read-only
* @mnt: the mount to check for its write status
*
* This shouldn't be used directly ouside of the VFS.
* It does not guarantee that the filesystem will stay
* r/w, just that it is right *now*. This can not and
* should not be used in place of IS_RDONLY(inode).
* mnt_want/drop_write() will _keep_ the filesystem
* r/w.
*/
int __mnt_is_readonly(struct vfsmount *mnt)
{
if (mnt->mnt_flags & MNT_READONLY)
return 1;
if (mnt->mnt_sb->s_flags & MS_RDONLY)
return 1;
return 0;
}
EXPORT_SYMBOL_GPL(__mnt_is_readonly);
static inline void inc_mnt_writers(struct vfsmount *mnt)
{
#ifdef CONFIG_SMP
(*per_cpu_ptr(mnt->mnt_writers, smp_processor_id()))++;
#else
mnt->mnt_writers++;
#endif
}
static inline void dec_mnt_writers(struct vfsmount *mnt)
#ifdef CONFIG_SMP
(*per_cpu_ptr(mnt->mnt_writers, smp_processor_id()))--;
#else
mnt->mnt_writers--;
#endif
static unsigned int count_mnt_writers(struct vfsmount *mnt)
#ifdef CONFIG_SMP
unsigned int count = 0;
int cpu;
for_each_possible_cpu(cpu) {
count += *per_cpu_ptr(mnt->mnt_writers, cpu);
return count;
#else
return mnt->mnt_writers;
#endif
/*
* Most r/o checks on a fs are for operations that take
* discrete amounts of time, like a write() or unlink().
* We must keep track of when those operations start
* (for permission checks) and when they end, so that
* we can determine when writes are able to occur to
* a filesystem.
*/
/**
* mnt_want_write - get write access to a mount
* @mnt: the mount on which to take a write
*
* This tells the low-level filesystem that a write is
* about to be performed to it, and makes sure that
* writes are allowed before returning success. When
* the write operation is finished, mnt_drop_write()
* must be called. This is effectively a refcount.
*/
int mnt_want_write(struct vfsmount *mnt)
{
int ret = 0;
preempt_disable();
inc_mnt_writers(mnt);
/*
* The store to inc_mnt_writers must be visible before we pass
* MNT_WRITE_HOLD loop below, so that the slowpath can see our
* incremented count after it has set MNT_WRITE_HOLD.
*/
smp_mb();
while (mnt->mnt_flags & MNT_WRITE_HOLD)
cpu_relax();
/*
* After the slowpath clears MNT_WRITE_HOLD, mnt_is_readonly will
* be set to match its requirements. So we must not load that until
* MNT_WRITE_HOLD is cleared.
*/
smp_rmb();
if (__mnt_is_readonly(mnt)) {
ret = -EROFS;
goto out;
}
out:
}
EXPORT_SYMBOL_GPL(mnt_want_write);
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/**
* mnt_clone_write - get write access to a mount
* @mnt: the mount on which to take a write
*
* This is effectively like mnt_want_write, except
* it must only be used to take an extra write reference
* on a mountpoint that we already know has a write reference
* on it. This allows some optimisation.
*
* After finished, mnt_drop_write must be called as usual to
* drop the reference.
*/
int mnt_clone_write(struct vfsmount *mnt)
{
/* superblock may be r/o */
if (__mnt_is_readonly(mnt))
return -EROFS;
preempt_disable();
inc_mnt_writers(mnt);
preempt_enable();
return 0;
}
EXPORT_SYMBOL_GPL(mnt_clone_write);
/**
* mnt_want_write_file - get write access to a file's mount
* @file: the file who's mount on which to take a write
*
* This is like mnt_want_write, but it takes a file and can
* do some optimisations if the file is open for write already
*/
int mnt_want_write_file(struct file *file)
{
struct inode *inode = file->f_dentry->d_inode;
if (!(file->f_mode & FMODE_WRITE) || special_file(inode->i_mode))
return mnt_want_write(file->f_path.mnt);
else
return mnt_clone_write(file->f_path.mnt);
}
EXPORT_SYMBOL_GPL(mnt_want_write_file);
/**
* mnt_drop_write - give up write access to a mount
* @mnt: the mount on which to give up write access
*
* Tells the low-level filesystem that we are done
* performing writes to it. Must be matched with
* mnt_want_write() call above.
*/
void mnt_drop_write(struct vfsmount *mnt)
{
preempt_disable();
dec_mnt_writers(mnt);
preempt_enable();
}
EXPORT_SYMBOL_GPL(mnt_drop_write);
static int mnt_make_readonly(struct vfsmount *mnt)
int ret = 0;
spin_lock(&vfsmount_lock);
mnt->mnt_flags |= MNT_WRITE_HOLD;
* After storing MNT_WRITE_HOLD, we'll read the counters. This store
* should be visible before we do.
* With writers on hold, if this value is zero, then there are
* definitely no active writers (although held writers may subsequently
* increment the count, they'll have to wait, and decrement it after
* seeing MNT_READONLY).
*
* It is OK to have counter incremented on one CPU and decremented on
* another: the sum will add up correctly. The danger would be when we
* sum up each counter, if we read a counter before it is incremented,
* but then read another CPU's count which it has been subsequently
* decremented from -- we would see more decrements than we should.
* MNT_WRITE_HOLD protects against this scenario, because
* mnt_want_write first increments count, then smp_mb, then spins on
* MNT_WRITE_HOLD, so it can't be decremented by another CPU while
* we're counting up here.
if (count_mnt_writers(mnt) > 0)
ret = -EBUSY;
else
mnt->mnt_flags |= MNT_READONLY;
/*
* MNT_READONLY must become visible before ~MNT_WRITE_HOLD, so writers
* that become unheld will see MNT_READONLY.
*/
smp_wmb();
mnt->mnt_flags &= ~MNT_WRITE_HOLD;
spin_unlock(&vfsmount_lock);
static void __mnt_unmake_readonly(struct vfsmount *mnt)
{
spin_lock(&vfsmount_lock);
mnt->mnt_flags &= ~MNT_READONLY;
spin_unlock(&vfsmount_lock);
}
void simple_set_mnt(struct vfsmount *mnt, struct super_block *sb)
{
mnt->mnt_sb = sb;
mnt->mnt_root = dget(sb->s_root);
}
EXPORT_SYMBOL(simple_set_mnt);
void free_vfsmnt(struct vfsmount *mnt)
{
kfree(mnt->mnt_devname);
#ifdef CONFIG_SMP
free_percpu(mnt->mnt_writers);
#endif
* 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.
struct vfsmount *__lookup_mnt(struct vfsmount *mnt, struct dentry *dentry,
int dir)
struct list_head *head = mount_hashtable + hash(mnt, dentry);
struct list_head *tmp = head;
p = NULL;
if (tmp == head)
break;
p = list_entry(tmp, struct vfsmount, mnt_hash);
if (p->mnt_parent == mnt && p->mnt_mountpoint == dentry) {
/*
* lookup_mnt increments the ref count before returning
* the vfsmount struct.
*/
{
struct vfsmount *child_mnt;
spin_lock(&vfsmount_lock);
if ((child_mnt = __lookup_mnt(path->mnt, path->dentry, 1)))
mntget(child_mnt);
spin_unlock(&vfsmount_lock);
return child_mnt;
}
static inline int check_mnt(struct vfsmount *mnt)
{
return mnt->mnt_ns == current->nsproxy->mnt_ns;
static void touch_mnt_namespace(struct mnt_namespace *ns)
{
if (ns) {
ns->event = ++event;
wake_up_interruptible(&ns->poll);
}
}
static void __touch_mnt_namespace(struct mnt_namespace *ns)
{
if (ns && ns->event != event) {
ns->event = event;
wake_up_interruptible(&ns->poll);
}
}
static void detach_mnt(struct vfsmount *mnt, struct path *old_path)
old_path->dentry = mnt->mnt_mountpoint;
old_path->mnt = mnt->mnt_parent;
mnt->mnt_parent = mnt;
mnt->mnt_mountpoint = mnt->mnt_root;
list_del_init(&mnt->mnt_child);
list_del_init(&mnt->mnt_hash);
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++;
}
static void attach_mnt(struct vfsmount *mnt, struct path *path)
mnt_set_mountpoint(path->mnt, path->dentry, mnt);
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(path->mnt, path->dentry));
list_add_tail(&mnt->mnt_child, &path->mnt->mnt_mounts);
}
/*
* 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);
struct mnt_namespace *n = parent->mnt_ns;
BUG_ON(parent == mnt);
list_add_tail(&head, &mnt->mnt_list);
list_for_each_entry(m, &head, mnt_list)
m->mnt_ns = n;
list_splice(&head, n->list.prev);
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(parent, mnt->mnt_mountpoint));
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
touch_mnt_namespace(n);
}
static struct vfsmount *next_mnt(struct vfsmount *p, struct vfsmount *root)
{
struct list_head *next = p->mnt_mounts.next;
if (next == &p->mnt_mounts) {
while (1) {
if (p == root)
return NULL;
next = p->mnt_child.next;
if (next != &p->mnt_parent->mnt_mounts)
break;
p = p->mnt_parent;
}
}
return list_entry(next, struct vfsmount, mnt_child);
}
static struct vfsmount *skip_mnt_tree(struct vfsmount *p)
{
struct list_head *prev = p->mnt_mounts.prev;
while (prev != &p->mnt_mounts) {
p = list_entry(prev, struct vfsmount, mnt_child);
prev = p->mnt_mounts.prev;
}
return p;
}
static struct vfsmount *clone_mnt(struct vfsmount *old, struct dentry *root,
int flag)
{
struct super_block *sb = old->mnt_sb;
struct vfsmount *mnt = alloc_vfsmnt(old->mnt_devname);
if (mnt) {
if (flag & (CL_SLAVE | CL_PRIVATE))
mnt->mnt_group_id = 0; /* not a peer of original */
else
mnt->mnt_group_id = old->mnt_group_id;
if ((flag & CL_MAKE_SHARED) && !mnt->mnt_group_id) {
int err = mnt_alloc_group_id(mnt);
if (err)
goto out_free;
}
mnt->mnt_flags = old->mnt_flags;
atomic_inc(&sb->s_active);
mnt->mnt_sb = sb;
mnt->mnt_root = dget(root);
mnt->mnt_mountpoint = mnt->mnt_root;
mnt->mnt_parent = mnt;
if (flag & CL_SLAVE) {
list_add(&mnt->mnt_slave, &old->mnt_slave_list);
mnt->mnt_master = old;
CLEAR_MNT_SHARED(mnt);
} else if (!(flag & CL_PRIVATE)) {
if ((flag & CL_MAKE_SHARED) || IS_MNT_SHARED(old))
list_add(&mnt->mnt_share, &old->mnt_share);
if (IS_MNT_SLAVE(old))
list_add(&mnt->mnt_slave, &old->mnt_slave);
mnt->mnt_master = old->mnt_master;
}
if (flag & CL_MAKE_SHARED)
set_mnt_shared(mnt);
/* stick the duplicate mount on the same expiry list
* as the original if that was on one */
if (flag & CL_EXPIRE) {
if (!list_empty(&old->mnt_expire))
list_add(&mnt->mnt_expire, &old->mnt_expire);
}
out_free:
free_vfsmnt(mnt);
return NULL;
static inline void __mntput(struct vfsmount *mnt)
/*
* This probably indicates that somebody messed
* up a mnt_want/drop_write() pair. If this
* happens, the filesystem was probably unable
* to make r/w->r/o transitions.
*/
/*
* atomic_dec_and_lock() used to deal with ->mnt_count decrements
* provides barriers, so count_mnt_writers() below is safe. AV
*/
WARN_ON(count_mnt_writers(mnt));
dput(mnt->mnt_root);
free_vfsmnt(mnt);
deactivate_super(sb);
}
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
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);
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);
static inline void mangle(struct seq_file *m, const char *s)
{
seq_escape(m, s, " \t\n\\");
}
/*
* Simple .show_options callback for filesystems which don't want to
* implement more complex mount option showing.
*
* See also save_mount_options().
*/
int generic_show_options(struct seq_file *m, struct vfsmount *mnt)
{
const char *options;
rcu_read_lock();
options = rcu_dereference(mnt->mnt_sb->s_options);
if (options != NULL && options[0]) {
seq_putc(m, ',');
mangle(m, options);
}
return 0;
}
EXPORT_SYMBOL(generic_show_options);
/*
* If filesystem uses generic_show_options(), this function should be
* called from the fill_super() callback.
*
* The .remount_fs callback usually needs to be handled in a special
* way, to make sure, that previous options are not overwritten if the
* remount fails.
*
* Also note, that if the filesystem's .remount_fs function doesn't
* reset all options to their default value, but changes only newly
* given options, then the displayed options will not reflect reality
* any more.
*/
void save_mount_options(struct super_block *sb, char *options)
{
BUG_ON(sb->s_options);
rcu_assign_pointer(sb->s_options, kstrdup(options, GFP_KERNEL));
}
EXPORT_SYMBOL(save_mount_options);
void replace_mount_options(struct super_block *sb, char *options)
{
char *old = sb->s_options;
rcu_assign_pointer(sb->s_options, options);
if (old) {
synchronize_rcu();
kfree(old);
}
}
EXPORT_SYMBOL(replace_mount_options);
#ifdef CONFIG_PROC_FS
/* iterator */
static void *m_start(struct seq_file *m, loff_t *pos)
{
struct proc_mounts *p = m->private;
return seq_list_start(&p->ns->list, *pos);
}
static void *m_next(struct seq_file *m, void *v, loff_t *pos)
{
struct proc_mounts *p = m->private;
return seq_list_next(v, &p->ns->list, pos);
}
static void m_stop(struct seq_file *m, void *v)
{
int mnt_had_events(struct proc_mounts *p)
{
struct mnt_namespace *ns = p->ns;
int res = 0;
spin_lock(&vfsmount_lock);
if (p->event != ns->event) {
p->event = ns->event;
res = 1;
}
spin_unlock(&vfsmount_lock);
return res;
}
struct proc_fs_info {
int flag;
const char *str;
};
static int show_sb_opts(struct seq_file *m, struct super_block *sb)
static const struct proc_fs_info fs_info[] = {
{ MS_SYNCHRONOUS, ",sync" },
{ MS_DIRSYNC, ",dirsync" },
{ MS_MANDLOCK, ",mand" },
{ 0, NULL }
};
const struct proc_fs_info *fs_infop;
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
if (sb->s_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}
return security_sb_show_options(m, sb);
}
static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt)
{
static const struct proc_fs_info mnt_info[] = {
{ MNT_NOSUID, ",nosuid" },
{ MNT_NODEV, ",nodev" },
{ MNT_NOEXEC, ",noexec" },
{ MNT_NOATIME, ",noatime" },
{ MNT_NODIRATIME, ",nodiratime" },
const struct proc_fs_info *fs_infop;
for (fs_infop = mnt_info; fs_infop->flag; fs_infop++) {
if (mnt->mnt_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}
}
static void show_type(struct seq_file *m, struct super_block *sb)
{
mangle(m, sb->s_type->name);
if (sb->s_subtype && sb->s_subtype[0]) {
seq_putc(m, '.');
mangle(m, sb->s_subtype);
}
}
static int show_vfsmnt(struct seq_file *m, void *v)
{
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
int err = 0;
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
seq_putc(m, ' ');
seq_path(m, &mnt_path, " \t\n\\");
show_type(m, mnt->mnt_sb);
seq_puts(m, __mnt_is_readonly(mnt) ? " ro" : " rw");
err = show_sb_opts(m, mnt->mnt_sb);
if (err)
goto out;
if (mnt->mnt_sb->s_op->show_options)
err = mnt->mnt_sb->s_op->show_options(m, mnt);
seq_puts(m, " 0 0\n");
const struct seq_operations mounts_op = {
.start = m_start,
.next = m_next,
.stop = m_stop,
.show = show_vfsmnt
};
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
static int show_mountinfo(struct seq_file *m, void *v)
{
struct proc_mounts *p = m->private;
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
struct super_block *sb = mnt->mnt_sb;
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
struct path root = p->root;
int err = 0;
seq_printf(m, "%i %i %u:%u ", mnt->mnt_id, mnt->mnt_parent->mnt_id,
MAJOR(sb->s_dev), MINOR(sb->s_dev));
seq_dentry(m, mnt->mnt_root, " \t\n\\");
seq_putc(m, ' ');
seq_path_root(m, &mnt_path, &root, " \t\n\\");
if (root.mnt != p->root.mnt || root.dentry != p->root.dentry) {
/*
* Mountpoint is outside root, discard that one. Ugly,
* but less so than trying to do that in iterator in a
* race-free way (due to renames).
*/
return SEQ_SKIP;
}
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
show_mnt_opts(m, mnt);
/* Tagged fields ("foo:X" or "bar") */
if (IS_MNT_SHARED(mnt))
seq_printf(m, " shared:%i", mnt->mnt_group_id);
if (IS_MNT_SLAVE(mnt)) {
int master = mnt->mnt_master->mnt_group_id;
int dom = get_dominating_id(mnt, &p->root);
seq_printf(m, " master:%i", master);
if (dom && dom != master)
seq_printf(m, " propagate_from:%i", dom);
}
if (IS_MNT_UNBINDABLE(mnt))
seq_puts(m, " unbindable");
/* Filesystem specific data */
seq_puts(m, " - ");
show_type(m, sb);
seq_putc(m, ' ');
mangle(m, mnt->mnt_devname ? mnt->mnt_devname : "none");
seq_puts(m, sb->s_flags & MS_RDONLY ? " ro" : " rw");
err = show_sb_opts(m, sb);
if (err)
goto out;
if (sb->s_op->show_options)
err = sb->s_op->show_options(m, mnt);
seq_putc(m, '\n');
return err;
}
const struct seq_operations mountinfo_op = {
.start = m_start,
.next = m_next,
.stop = m_stop,
.show = show_mountinfo,
};
static int show_vfsstat(struct seq_file *m, void *v)
{
struct vfsmount *mnt = list_entry(v, struct vfsmount, mnt_list);
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
int err = 0;
/* device */
if (mnt->mnt_devname) {
seq_puts(m, "device ");
mangle(m, mnt->mnt_devname);
} else
seq_puts(m, "no device");
/* mount point */
seq_puts(m, " mounted on ");
seq_path(m, &mnt_path, " \t\n\\");
seq_putc(m, ' ');
/* file system type */
seq_puts(m, "with fstype ");
show_type(m, mnt->mnt_sb);
/* optional statistics */
if (mnt->mnt_sb->s_op->show_stats) {
seq_putc(m, ' ');
err = mnt->mnt_sb->s_op->show_stats(m, mnt);
}
seq_putc(m, '\n');
return err;
}
const struct seq_operations mountstats_op = {
.start = m_start,
.next = m_next,
.stop = m_stop,
.show = show_vfsstat,
};
#endif /* CONFIG_PROC_FS */
/**
* may_umount_tree - check if a mount tree is busy
* @mnt: root of mount tree
*
* This is called to check if a tree of mounts has any
* open files, pwds, chroots or sub mounts that are
* busy.
*/
int may_umount_tree(struct vfsmount *mnt)
{
int actual_refs = 0;
int minimum_refs = 0;
struct vfsmount *p;
actual_refs += atomic_read(&p->mnt_count);
minimum_refs += 2;
}
spin_unlock(&vfsmount_lock);
if (actual_refs > minimum_refs)
}
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)
{
spin_lock(&vfsmount_lock);
if (propagate_mount_busy(mnt, 2))
void release_mounts(struct list_head *head)
mnt = list_first_entry(head, struct vfsmount, mnt_hash);
list_del_init(&mnt->mnt_hash);
if (mnt->mnt_parent != mnt) {
struct dentry *dentry;