android_kernel_samsung_msm8976/fs/proc_namespace.c
Daniel Rosenberg de76c2fe9b vfs: Allow filesystems to access their private mount data
Now we pass the vfsmount when mounting and remounting.
This allows the filesystem to actually set up the mount
specific data, although we can't quite do anything with
it yet. show_options is expanded to include data that
lives with the mount.

To avoid changing existing filesystems, these have
been added as new vfs functions.

Change-Id: If80670bfad9f287abb8ac22457e1b034c9697097
Signed-off-by: Daniel Rosenberg <drosen@google.com>
2018-02-06 13:12:19 +01:00

337 lines
7.7 KiB
C

/*
* fs/proc_namespace.c - handling of /proc/<pid>/{mounts,mountinfo,mountstats}
*
* In fact, that's a piece of procfs; it's *almost* isolated from
* the rest of fs/proc, but has rather close relationships with
* fs/namespace.c, thus here instead of fs/proc
*
*/
#include <linux/mnt_namespace.h>
#include <linux/nsproxy.h>
#include <linux/security.h>
#include <linux/fs_struct.h>
#include "proc/internal.h" /* only for get_proc_task() in ->open() */
#include "pnode.h"
#include "internal.h"
static unsigned mounts_poll(struct file *file, poll_table *wait)
{
struct proc_mounts *p = proc_mounts(file->private_data);
struct mnt_namespace *ns = p->ns;
unsigned res = POLLIN | POLLRDNORM;
poll_wait(file, &p->ns->poll, wait);
br_read_lock(&vfsmount_lock);
if (p->m.poll_event != ns->event) {
p->m.poll_event = ns->event;
res |= POLLERR | POLLPRI;
}
br_read_unlock(&vfsmount_lock);
return res;
}
struct proc_fs_info {
int flag;
const char *str;
};
static int show_sb_opts(struct seq_file *m, struct super_block *sb)
{
static const struct proc_fs_info fs_info[] = {
{ MS_SYNCHRONOUS, ",sync" },
{ MS_DIRSYNC, ",dirsync" },
{ MS_MANDLOCK, ",mand" },
{ 0, NULL }
};
const struct proc_fs_info *fs_infop;
for (fs_infop = fs_info; fs_infop->flag; fs_infop++) {
if (sb->s_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}
return security_sb_show_options(m, sb);
}
static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt)
{
static const struct proc_fs_info mnt_info[] = {
{ MNT_NOSUID, ",nosuid" },
{ MNT_NODEV, ",nodev" },
{ MNT_NOEXEC, ",noexec" },
{ MNT_NOATIME, ",noatime" },
{ MNT_NODIRATIME, ",nodiratime" },
{ MNT_RELATIME, ",relatime" },
{ 0, NULL }
};
const struct proc_fs_info *fs_infop;
for (fs_infop = mnt_info; fs_infop->flag; fs_infop++) {
if (mnt->mnt_flags & fs_infop->flag)
seq_puts(m, fs_infop->str);
}
}
static inline void mangle(struct seq_file *m, const char *s)
{
seq_escape(m, s, " \t\n\\");
}
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, struct vfsmount *mnt)
{
struct mount *r = real_mount(mnt);
int err = 0;
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
struct super_block *sb = mnt_path.dentry->d_sb;
if (sb->s_op->show_devname) {
err = sb->s_op->show_devname(m, mnt_path.dentry);
if (err)
goto out;
} else {
mangle(m, r->mnt_devname ? r->mnt_devname : "none");
}
seq_putc(m, ' ');
seq_path(m, &mnt_path, " \t\n\\");
seq_putc(m, ' ');
show_type(m, sb);
seq_puts(m, __mnt_is_readonly(mnt) ? " ro" : " rw");
err = show_sb_opts(m, sb);
if (err)
goto out;
show_mnt_opts(m, mnt);
if (sb->s_op->show_options2)
err = sb->s_op->show_options2(mnt, m, mnt_path.dentry);
else if (sb->s_op->show_options)
err = sb->s_op->show_options(m, mnt_path.dentry);
seq_puts(m, " 0 0\n");
out:
return err;
}
static int show_mountinfo(struct seq_file *m, struct vfsmount *mnt)
{
struct proc_mounts *p = proc_mounts(m);
struct mount *r = real_mount(mnt);
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 ", r->mnt_id, r->mnt_parent->mnt_id,
MAJOR(sb->s_dev), MINOR(sb->s_dev));
if (sb->s_op->show_path)
err = sb->s_op->show_path(m, mnt->mnt_root);
else
seq_dentry(m, mnt->mnt_root, " \t\n\\");
if (err)
goto out;
seq_putc(m, ' ');
/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
err = seq_path_root(m, &mnt_path, &root, " \t\n\\");
if (err)
goto out;
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(r))
seq_printf(m, " shared:%i", r->mnt_group_id);
if (IS_MNT_SLAVE(r)) {
int master = r->mnt_master->mnt_group_id;
int dom = get_dominating_id(r, &p->root);
seq_printf(m, " master:%i", master);
if (dom && dom != master)
seq_printf(m, " propagate_from:%i", dom);
}
if (IS_MNT_UNBINDABLE(r))
seq_puts(m, " unbindable");
/* Filesystem specific data */
seq_puts(m, " - ");
show_type(m, sb);
seq_putc(m, ' ');
if (sb->s_op->show_devname)
err = sb->s_op->show_devname(m, mnt->mnt_root);
else
mangle(m, r->mnt_devname ? r->mnt_devname : "none");
if (err)
goto out;
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_options2) {
err = sb->s_op->show_options2(mnt, m, mnt->mnt_root);
} else if (sb->s_op->show_options)
err = sb->s_op->show_options(m, mnt->mnt_root);
seq_putc(m, '\n');
out:
return err;
}
static int show_vfsstat(struct seq_file *m, struct vfsmount *mnt)
{
struct mount *r = real_mount(mnt);
struct path mnt_path = { .dentry = mnt->mnt_root, .mnt = mnt };
struct super_block *sb = mnt_path.dentry->d_sb;
int err = 0;
/* device */
if (sb->s_op->show_devname) {
seq_puts(m, "device ");
err = sb->s_op->show_devname(m, mnt_path.dentry);
} else {
if (r->mnt_devname) {
seq_puts(m, "device ");
mangle(m, r->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, sb);
/* optional statistics */
if (sb->s_op->show_stats) {
seq_putc(m, ' ');
if (!err)
err = sb->s_op->show_stats(m, mnt_path.dentry);
}
seq_putc(m, '\n');
return err;
}
static int mounts_open_common(struct inode *inode, struct file *file,
int (*show)(struct seq_file *, struct vfsmount *))
{
struct task_struct *task = get_proc_task(inode);
struct nsproxy *nsp;
struct mnt_namespace *ns = NULL;
struct path root;
struct proc_mounts *p;
int ret = -EINVAL;
if (!task)
goto err;
rcu_read_lock();
nsp = task_nsproxy(task);
if (!nsp) {
rcu_read_unlock();
put_task_struct(task);
goto err;
}
ns = nsp->mnt_ns;
if (!ns) {
rcu_read_unlock();
put_task_struct(task);
goto err;
}
get_mnt_ns(ns);
rcu_read_unlock();
task_lock(task);
if (!task->fs) {
task_unlock(task);
put_task_struct(task);
ret = -ENOENT;
goto err_put_ns;
}
get_fs_root(task->fs, &root);
task_unlock(task);
put_task_struct(task);
ret = -ENOMEM;
p = kmalloc(sizeof(struct proc_mounts), GFP_KERNEL);
if (!p)
goto err_put_path;
file->private_data = &p->m;
ret = seq_open(file, &mounts_op);
if (ret)
goto err_free;
p->ns = ns;
p->root = root;
p->m.poll_event = ns->event;
p->show = show;
return 0;
err_free:
kfree(p);
err_put_path:
path_put(&root);
err_put_ns:
put_mnt_ns(ns);
err:
return ret;
}
static int mounts_release(struct inode *inode, struct file *file)
{
struct proc_mounts *p = proc_mounts(file->private_data);
path_put(&p->root);
put_mnt_ns(p->ns);
return seq_release(inode, file);
}
static int mounts_open(struct inode *inode, struct file *file)
{
return mounts_open_common(inode, file, show_vfsmnt);
}
static int mountinfo_open(struct inode *inode, struct file *file)
{
return mounts_open_common(inode, file, show_mountinfo);
}
static int mountstats_open(struct inode *inode, struct file *file)
{
return mounts_open_common(inode, file, show_vfsstat);
}
const struct file_operations proc_mounts_operations = {
.open = mounts_open,
.read = seq_read,
.llseek = seq_lseek,
.release = mounts_release,
.poll = mounts_poll,
};
const struct file_operations proc_mountinfo_operations = {
.open = mountinfo_open,
.read = seq_read,
.llseek = seq_lseek,
.release = mounts_release,
.poll = mounts_poll,
};
const struct file_operations proc_mountstats_operations = {
.open = mountstats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = mounts_release,
};