drivers/tty/pty.c | 2 +- fs/devpts/inode.c | 107 +++++++++++++++++++++++++++++----------------- fs/namei.c | 2 +- include/linux/devpts_fs.h | 2 +- 4 files changed, 70 insertions(+), 43 deletions(-) diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 0058d9fbf931..2a567d0433a2 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -67,7 +67,7 @@ static void pty_close(struct tty_struct *tty, struct file *filp) if (tty->driver == ptm_driver) { mutex_lock(&devpts_mutex); if (tty->link->driver_data) - devpts_pty_kill(tty->link->driver_data); + devpts_pty_kill(tty->driver_data, tty->link->driver_data); mutex_unlock(&devpts_mutex); } #endif diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c index 0af8e7d70d27..e36a4721eec2 100644 --- a/fs/devpts/inode.c +++ b/fs/devpts/inode.c @@ -137,17 +137,6 @@ static inline struct pts_fs_info *DEVPTS_SB(struct super_block *sb) return sb->s_fs_info; } -static inline struct super_block *pts_sb_from_inode(struct inode *inode) -{ -#ifdef CONFIG_DEVPTS_MULTIPLE_INSTANCES - if (inode->i_sb->s_magic == DEVPTS_SUPER_MAGIC) - return inode->i_sb; -#endif - if (!devpts_mnt) - return NULL; - return devpts_mnt->mnt_sb; -} - #define PARSE_MOUNT 0 #define PARSE_REMOUNT 1 @@ -411,13 +400,6 @@ fail: } #ifdef CONFIG_DEVPTS_MULTIPLE_INSTANCES -static int compare_init_pts_sb(struct super_block *s, void *p) -{ - if (devpts_mnt) - return devpts_mnt->mnt_sb == s; - return 0; -} - /* * devpts_mount() * @@ -456,17 +438,7 @@ static struct dentry *devpts_mount(struct file_system_type *fs_type, if (error) return ERR_PTR(error); - /* Require newinstance for all user namespace mounts to ensure - * the mount options are not changed. - */ - if ((current_user_ns() != &init_user_ns) && !opts.newinstance) - return ERR_PTR(-EINVAL); - - if (opts.newinstance) - s = sget(fs_type, NULL, set_anon_super, flags, NULL); - else - s = sget(fs_type, compare_init_pts_sb, set_anon_super, flags, - NULL); + s = sget(fs_type, NULL, set_anon_super, flags, NULL); if (IS_ERR(s)) return ERR_CAST(s); @@ -571,23 +543,78 @@ void devpts_kill_index(struct pts_fs_info *fsi, int idx) mutex_unlock(&allocated_ptys_lock); } +extern void follow_mount(struct path *path); + +static struct dentry *ptmx_to_pts(struct path *ptmx) +{ + struct dentry *dev = dget_parent(ptmx->dentry); + + if (dev) { + struct path path; + + path.dentry = lookup_one_len_unlocked("pts", dev, 3); + if (path.dentry) { + path.mnt = mntget(ptmx->mnt); + follow_mount(&path); + mntput(path.mnt); + return path.dentry; + } + } + return NULL; +} + +static struct super_block *get_devpts_sb(struct inode *ptmx_inode, struct file *file) +{ + struct super_block *sb; + struct dentry *pts; + + /* If it's the ptmx node in a devpts filesystem, we're done */ + sb = ptmx_inode->i_sb; + if (sb->s_magic == DEVPTS_SUPER_MAGIC) { + atomic_inc(&sb->s_active); + return sb; + } + + /* Otherwise, try to look up ptmx -> pts/ relationship */ + pts = ptmx_to_pts(&file->f_path); +WARN_ON_ONCE(!pts); + if (pts) { + sb = pts->d_sb; +WARN_ON_ONCE(sb->s_magic != DEVPTS_SUPER_MAGIC); + if (sb->s_magic == DEVPTS_SUPER_MAGIC) { + atomic_inc(&sb->s_active); + dput(pts); + return sb; + } + } + + /* This is disgusting, old, and wrong. We do it for legacy reasons */ + if (devpts_mnt) { + sb = devpts_mnt->mnt_sb; + atomic_inc(&sb->s_active); + return sb; + } + return NULL; +} + + /* * pty code needs to hold extra references in case of last /dev/tty close */ struct pts_fs_info *devpts_get_ref(struct inode *ptmx_inode, struct file *file) { struct super_block *sb; - struct pts_fs_info *fsi; - sb = pts_sb_from_inode(ptmx_inode); - if (!sb) - return NULL; - fsi = DEVPTS_SB(sb); - if (!fsi) - return NULL; + sb = get_devpts_sb(ptmx_inode, file); + if (sb) { + struct pts_fs_info *fsi; - atomic_inc(&sb->s_active); - return fsi; + fsi = DEVPTS_SB(sb); + if (fsi) + return fsi; + deactivate_super(sb); + } + return NULL; } void devpts_put_ref(struct pts_fs_info *fsi) @@ -682,9 +709,9 @@ void *devpts_get_priv(struct inode *pts_inode) * * This is an inverse operation of devpts_pty_new. */ -void devpts_pty_kill(struct inode *inode) +void devpts_pty_kill(struct pts_fs_info *fsi, struct inode *inode) { - struct super_block *sb = pts_sb_from_inode(inode); + struct super_block *sb = fsi->sb; struct dentry *root = sb->s_root; struct dentry *dentry; diff --git a/fs/namei.c b/fs/namei.c index 1d9ca2d5dff6..5329efd11a3e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1402,7 +1402,7 @@ EXPORT_SYMBOL(follow_down); /* * Skip to top of mountpoint pile in refwalk mode for follow_dotdot() */ -static void follow_mount(struct path *path) +void follow_mount(struct path *path) { while (d_mountpoint(path->dentry)) { struct vfsmount *mounted = lookup_mnt(path); diff --git a/include/linux/devpts_fs.h b/include/linux/devpts_fs.h index 358a4db72a27..95d17243605e 100644 --- a/include/linux/devpts_fs.h +++ b/include/linux/devpts_fs.h @@ -31,7 +31,7 @@ struct inode *devpts_pty_new(struct pts_fs_info *, dev_t, int, void *); /* get private structure */ void *devpts_get_priv(struct inode *pts_inode); /* unlink */ -void devpts_pty_kill(struct inode *inode); +void devpts_pty_kill(struct pts_fs_info *, struct inode *inode); #endif