Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[cascardo/linux.git] / fs / nsfs.c
index 2b731bc..8718af8 100644 (file)
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -5,11 +5,16 @@
 #include <linux/magic.h>
 #include <linux/ktime.h>
 #include <linux/seq_file.h>
 #include <linux/magic.h>
 #include <linux/ktime.h>
 #include <linux/seq_file.h>
+#include <linux/user_namespace.h>
+#include <linux/nsfs.h>
 
 static struct vfsmount *nsfs_mnt;
 
 
 static struct vfsmount *nsfs_mnt;
 
+static long ns_ioctl(struct file *filp, unsigned int ioctl,
+                       unsigned long arg);
 static const struct file_operations ns_file_operations = {
        .llseek         = no_llseek,
 static const struct file_operations ns_file_operations = {
        .llseek         = no_llseek,
+       .unlocked_ioctl = ns_ioctl,
 };
 
 static char *ns_dname(struct dentry *dentry, char *buffer, int buflen)
 };
 
 static char *ns_dname(struct dentry *dentry, char *buffer, int buflen)
@@ -44,22 +49,14 @@ static void nsfs_evict(struct inode *inode)
        ns->ops->put(ns);
 }
 
        ns->ops->put(ns);
 }
 
-void *ns_get_path(struct path *path, struct task_struct *task,
-                       const struct proc_ns_operations *ns_ops)
+static void *__ns_get_path(struct path *path, struct ns_common *ns)
 {
 {
-       struct vfsmount *mnt = mntget(nsfs_mnt);
+       struct vfsmount *mnt = nsfs_mnt;
        struct qstr qname = { .name = "", };
        struct dentry *dentry;
        struct inode *inode;
        struct qstr qname = { .name = "", };
        struct dentry *dentry;
        struct inode *inode;
-       struct ns_common *ns;
        unsigned long d;
 
        unsigned long d;
 
-again:
-       ns = ns_ops->get(task);
-       if (!ns) {
-               mntput(mnt);
-               return ERR_PTR(-ENOENT);
-       }
        rcu_read_lock();
        d = atomic_long_read(&ns->stashed);
        if (!d)
        rcu_read_lock();
        d = atomic_long_read(&ns->stashed);
        if (!d)
@@ -68,17 +65,16 @@ again:
        if (!lockref_get_not_dead(&dentry->d_lockref))
                goto slow;
        rcu_read_unlock();
        if (!lockref_get_not_dead(&dentry->d_lockref))
                goto slow;
        rcu_read_unlock();
-       ns_ops->put(ns);
+       ns->ops->put(ns);
 got_it:
 got_it:
-       path->mnt = mnt;
+       path->mnt = mntget(mnt);
        path->dentry = dentry;
        return NULL;
 slow:
        rcu_read_unlock();
        inode = new_inode_pseudo(mnt->mnt_sb);
        if (!inode) {
        path->dentry = dentry;
        return NULL;
 slow:
        rcu_read_unlock();
        inode = new_inode_pseudo(mnt->mnt_sb);
        if (!inode) {
-               ns_ops->put(ns);
-               mntput(mnt);
+               ns->ops->put(ns);
                return ERR_PTR(-ENOMEM);
        }
        inode->i_ino = ns->inum;
                return ERR_PTR(-ENOMEM);
        }
        inode->i_ino = ns->inum;
@@ -91,21 +87,96 @@ slow:
        dentry = d_alloc_pseudo(mnt->mnt_sb, &qname);
        if (!dentry) {
                iput(inode);
        dentry = d_alloc_pseudo(mnt->mnt_sb, &qname);
        if (!dentry) {
                iput(inode);
-               mntput(mnt);
                return ERR_PTR(-ENOMEM);
        }
        d_instantiate(dentry, inode);
                return ERR_PTR(-ENOMEM);
        }
        d_instantiate(dentry, inode);
-       dentry->d_fsdata = (void *)ns_ops;
+       dentry->d_fsdata = (void *)ns->ops;
        d = atomic_long_cmpxchg(&ns->stashed, 0, (unsigned long)dentry);
        if (d) {
                d_delete(dentry);       /* make sure ->d_prune() does nothing */
                dput(dentry);
                cpu_relax();
        d = atomic_long_cmpxchg(&ns->stashed, 0, (unsigned long)dentry);
        if (d) {
                d_delete(dentry);       /* make sure ->d_prune() does nothing */
                dput(dentry);
                cpu_relax();
-               goto again;
+               return ERR_PTR(-EAGAIN);
        }
        goto got_it;
 }
 
        }
        goto got_it;
 }
 
+void *ns_get_path(struct path *path, struct task_struct *task,
+                       const struct proc_ns_operations *ns_ops)
+{
+       struct ns_common *ns;
+       void *ret;
+
+again:
+       ns = ns_ops->get(task);
+       if (!ns)
+               return ERR_PTR(-ENOENT);
+
+       ret = __ns_get_path(path, ns);
+       if (IS_ERR(ret) && PTR_ERR(ret) == -EAGAIN)
+               goto again;
+       return ret;
+}
+
+static int open_related_ns(struct ns_common *ns,
+                  struct ns_common *(*get_ns)(struct ns_common *ns))
+{
+       struct path path = {};
+       struct file *f;
+       void *err;
+       int fd;
+
+       fd = get_unused_fd_flags(O_CLOEXEC);
+       if (fd < 0)
+               return fd;
+
+       while (1) {
+               struct ns_common *relative;
+
+               relative = get_ns(ns);
+               if (IS_ERR(relative)) {
+                       put_unused_fd(fd);
+                       return PTR_ERR(relative);
+               }
+
+               err = __ns_get_path(&path, relative);
+               if (IS_ERR(err) && PTR_ERR(err) == -EAGAIN)
+                       continue;
+               break;
+       }
+       if (IS_ERR(err)) {
+               put_unused_fd(fd);
+               return PTR_ERR(err);
+       }
+
+       f = dentry_open(&path, O_RDONLY, current_cred());
+       path_put(&path);
+       if (IS_ERR(f)) {
+               put_unused_fd(fd);
+               fd = PTR_ERR(f);
+       } else
+               fd_install(fd, f);
+
+       return fd;
+}
+
+static long ns_ioctl(struct file *filp, unsigned int ioctl,
+                       unsigned long arg)
+{
+       struct ns_common *ns = get_proc_ns(file_inode(filp));
+
+       switch (ioctl) {
+       case NS_GET_USERNS:
+               return open_related_ns(ns, ns_get_owner);
+       case NS_GET_PARENT:
+               if (!ns->ops->get_parent)
+                       return -EINVAL;
+               return open_related_ns(ns, ns->ops->get_parent);
+       default:
+               return -ENOTTY;
+       }
+}
+
 int ns_get_name(char *buf, size_t size, struct task_struct *task,
                        const struct proc_ns_operations *ns_ops)
 {
 int ns_get_name(char *buf, size_t size, struct task_struct *task,
                        const struct proc_ns_operations *ns_ops)
 {