This is in preparation for forcing each mount of devpts to be a
distinct filesystem.  The goal of this change is to cleanly allow
each mount of devpts to be a distince filesystem while not
introducing regressions in userspace.

On each open of /dev/ptmx look at the relative path ../pts and see if
devpts is mounted there.

If a devpts filesystem is found via the path lookup mount it's
ptmx node on /dev/ptmx.

If no devpts filesystem is found via the path lookup mount the
system devpts ptmx node on /dev/ptmx.  This retains backwards
compatibility for weird setups.

This winds up using 3 new vfs helpers path_parent, path_pts, and
vfs_loopback_mount.

Additionally init_special_inode and follow_automount are updated
with calls to is_dev_ptmx to add a tiny bit of extra code in
those functions to allow /dev/ptmx to hook into the automount
path.

I endeavored to keep the vfs changes clean, but I did not strive for
generality.

Signed-off-by: "Eric W. Biederman" <[email protected]>
---
 fs/devpts/inode.c         | 55 +++++++++++++++++++++++++++++++
 fs/inode.c                |  3 ++
 fs/namei.c                | 83 +++++++++++++++++++++++++++++++++++++++--------
 include/linux/devpts_fs.h | 13 ++++++++
 include/linux/namei.h     |  2 ++
 5 files changed, 143 insertions(+), 13 deletions(-)

diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c
index 4fc6c49b0efd..0b84063a1e14 100644
--- a/fs/devpts/inode.c
+++ b/fs/devpts/inode.c
@@ -17,6 +17,7 @@
 #include <linux/fs.h>
 #include <linux/sched.h>
 #include <linux/namei.h>
+#include <linux/fs_struct.h>
 #include <linux/slab.h>
 #include <linux/mount.h>
 #include <linux/tty.h>
@@ -695,6 +696,60 @@ void devpts_pty_kill(struct inode *inode)
        inode_unlock(d_inode(root));
 }
 
+static void ptmx_expire_automounts(struct work_struct *work);
+static LIST_HEAD(ptmx_automounts);
+static DECLARE_DELAYED_WORK(ptmx_automount_work, ptmx_expire_automounts);
+static unsigned long ptmx_automount_timeout = 10 * 60 * HZ;
+
+static void ptmx_expire_automounts(struct work_struct *work)
+{
+       struct list_head *list = &ptmx_automounts;
+
+       mark_mounts_for_expiry(list);
+       if (!list_empty(list))
+               schedule_delayed_work(&ptmx_automount_work,
+                                     ptmx_automount_timeout);
+}
+
+struct vfsmount *ptmx_automount(struct path *input_path)
+{
+       struct vfsmount *newmnt;
+       struct path path;
+       struct dentry *old;
+
+       /* Can the pts filesystem be found with a path walk? */
+       path = *input_path;
+       path_get(&path);
+
+       if ((path_pts(&path) != 0) ||
+           /* Is the path the root of a devpts filesystem? */
+           (path.mnt->mnt_sb->s_magic != DEVPTS_SUPER_MAGIC) ||
+           (path.mnt->mnt_root != path.mnt->mnt_sb->s_root)) {
+               /* No devpts filesystem found use the system devpts */
+               path_put(&path);
+               path.mnt = devpts_mnt;
+               path.dentry = DEVPTS_SB(devpts_mnt->mnt_sb)->ptmx_dentry;
+               path_get(&path);
+       }
+       else {
+               /* Advance path to the ptmx dentry */
+               old = path.dentry;
+               path.dentry = dget(DEVPTS_SB(path.mnt->mnt_sb)->ptmx_dentry);
+               dput(old);
+       }
+
+       newmnt = vfs_loopback_mount(&path);
+       if (IS_ERR(newmnt))
+               goto fail;
+
+       mntget(newmnt);
+       mnt_set_expiry(newmnt, &ptmx_automounts);
+       schedule_delayed_work(&ptmx_automount_work, ptmx_automount_timeout);
+fail:
+       path_put(&path);
+       return newmnt;
+}
+
 static int __init init_devpts_fs(void)
 {
        int err = register_filesystem(&devpts_fs_type);
diff --git a/fs/inode.c b/fs/inode.c
index 69b8b526c194..251330ba336e 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -18,6 +18,7 @@
 #include <linux/buffer_head.h> /* for inode_has_buffers */
 #include <linux/ratelimit.h>
 #include <linux/list_lru.h>
+#include <linux/devpts_fs.h>
 #include <trace/events/writeback.h>
 #include "internal.h"
 
@@ -1917,6 +1918,8 @@ void init_special_inode(struct inode *inode, umode_t 
mode, dev_t rdev)
        if (S_ISCHR(mode)) {
                inode->i_fop = &def_chr_fops;
                inode->i_rdev = rdev;
+               if (is_dev_ptmx(inode))
+                       inode->i_flags |= S_AUTOMOUNT;
        } else if (S_ISBLK(mode)) {
                inode->i_fop = &def_blk_fops;
                inode->i_rdev = rdev;
diff --git a/fs/namei.c b/fs/namei.c
index 794f81dce766..a4bdbeec8067 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -35,6 +35,7 @@
 #include <linux/fs_struct.h>
 #include <linux/posix_acl.h>
 #include <linux/hash.h>
+#include <linux/devpts_fs.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -1087,10 +1088,15 @@ EXPORT_SYMBOL(follow_up);
 static int follow_automount(struct path *path, struct nameidata *nd,
                            bool *need_mntput)
 {
+       struct vfsmount *(*automount)(struct path *) = NULL;
        struct vfsmount *mnt;
        int err;
 
-       if (!path->dentry->d_op || !path->dentry->d_op->d_automount)
+       if (path->dentry->d_op)
+               automount = path->dentry->d_op->d_automount;
+       if (path->dentry->d_inode && is_dev_ptmx(path->dentry->d_inode))
+               automount = ptmx_automount;
+       if (!automount)
                return -EREMOTE;
 
        /* We don't want to mount if someone's just doing a stat -
@@ -1113,7 +1119,7 @@ static int follow_automount(struct path *path, struct 
nameidata *nd,
        if (nd->total_link_count >= 40)
                return -ELOOP;
 
-       mnt = path->dentry->d_op->d_automount(path);
+       mnt = automount(path);
        if (IS_ERR(mnt)) {
                /*
                 * The filesystem is allowed to return -EISDIR here to indicate
@@ -1415,29 +1421,41 @@ static void follow_mount(struct path *path)
        }
 }
 
-static int follow_dotdot(struct nameidata *nd)
+static int path_parent(struct path *root, struct path *path)
 {
+       int ret = 0;
+
        while(1) {
-               struct dentry *old = nd->path.dentry;
+               struct dentry *old = path->dentry;
 
-               if (nd->path.dentry == nd->root.dentry &&
-                   nd->path.mnt == nd->root.mnt) {
+               if (old == root->dentry &&
+                   path->mnt == root->mnt) {
                        break;
                }
-               if (nd->path.dentry != nd->path.mnt->mnt_root) {
+               if (old != path->mnt->mnt_root) {
                        /* rare case of legitimate dget_parent()... */
-                       nd->path.dentry = dget_parent(nd->path.dentry);
+                       path->dentry = dget_parent(path->dentry);
                        dput(old);
-                       if (unlikely(!path_connected(&nd->path)))
+                       if (unlikely(!path_connected(path)))
                                return -ENOENT;
+                       ret = 1;
                        break;
                }
-               if (!follow_up(&nd->path))
+               if (!follow_up(path))
                        break;
        }
-       follow_mount(&nd->path);
-       nd->inode = nd->path.dentry->d_inode;
-       return 0;
+       follow_mount(path);
+       return ret;
+}
+
+static int follow_dotdot(struct nameidata *nd)
+{
+       int ret = path_parent(&nd->root, &nd->path);
+       if (ret >= 0) {
+               ret = 0;
+               nd->inode = nd->path.dentry->d_inode;
+       }
+       return ret;
 }
 
 /*
@@ -2374,6 +2392,45 @@ struct dentry *lookup_one_len_unlocked(const char *name,
 }
 EXPORT_SYMBOL(lookup_one_len_unlocked);
 
+#ifdef CONFIG_UNIX98_PTYS
+int path_pts(struct path *path)
+{
+       /* A pathwalk of "../pts" with no permission checks. */
+       struct dentry *child, *parent = path->dentry;
+       struct qstr this;
+       struct path root;
+       int ret;
+
+       get_fs_root(current->fs, &root);
+       ret = path_parent(&root, path);
+       path_put(&root);
+       if (ret != 1)
+               return -ENOENT;
+
+       if (!d_can_lookup(parent))
+               return -ENOENT;
+
+       this.name = "pts";
+       this.len = 3;
+       this.hash = full_name_hash(this.name, this.len);
+       if (parent->d_flags & DCACHE_OP_HASH) {
+               int err = parent->d_op->d_hash(parent, &this);
+               if (err < 0)
+                       return err;
+       }
+       inode_lock(parent->d_inode);
+       child = d_lookup(parent, &this);
+       inode_unlock(parent->d_inode);
+       if (!child)
+               return -ENOENT;
+
+       path->dentry = child;
+       dput(parent);
+       follow_mount(path);
+       return 0;
+}
+#endif
+
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
                 struct path *path, int *empty)
 {
diff --git a/include/linux/devpts_fs.h b/include/linux/devpts_fs.h
index ff2b7c274435..5b2f6d6cd386 100644
--- a/include/linux/devpts_fs.h
+++ b/include/linux/devpts_fs.h
@@ -19,9 +19,12 @@
 #define PTMX_MINOR     2
 
 #ifdef CONFIG_UNIX98_PTYS
+#include <linux/major.h>
 
 extern struct file_operations ptmx_fops;
 
+struct vfsmount *ptmx_automount(struct path *path);
+
 int devpts_new_index(struct inode *ptmx_inode);
 void devpts_kill_index(struct inode *ptmx_inode, int idx);
 void devpts_add_ref(struct inode *ptmx_inode);
@@ -34,6 +37,10 @@ void *devpts_get_priv(struct inode *pts_inode);
 /* unlink */
 void devpts_pty_kill(struct inode *inode);
 
+static inline bool is_dev_ptmx(struct inode *inode)
+{
+       return inode->i_rdev == MKDEV(TTYAUX_MAJOR, PTMX_MINOR);
+}
 #else
 
 /* Dummy stubs in the no-pty case */
@@ -52,6 +59,12 @@ static inline void *devpts_get_priv(struct inode *pts_inode)
 }
 static inline void devpts_pty_kill(struct inode *inode) { }
 
+#define ptmx_automount NULL
+
+static inline bool is_dev_ptmx(struct inode *inode)
+{
+       return false;
+}
 #endif
 
 
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 77d01700daf7..f29abda31e6d 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -45,6 +45,8 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
 #define LOOKUP_ROOT            0x2000
 #define LOOKUP_EMPTY           0x4000
 
+extern int path_pts(struct path *path);
+
 extern int user_path_at_empty(int, const char __user *, unsigned, struct path 
*, int *empty);
 
 static inline int user_path_at(int dfd, const char __user *name, unsigned 
flags,
-- 
2.8.1

Reply via email to