From 52494eeb993ef6865f7817c8b473f798771371fc Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 5 Nov 2014 12:12:50 -0500 Subject: [PATCH] tty: Re-open /dev/tty without tty_mutex Opening /dev/tty (ie., the controlling tty for the current task) is always a re-open of the underlying tty. Because holding the tty_lock is sufficient for safely re-opening a tty, and because having a tty kref is sufficient for safely acquiring the tty_lock [1], tty_open_current_tty() does not require holding tty_mutex. Repurpose tty_open_current_tty() to perform the re-open itself and refactor tty_open(). [1] Analysis of safely re-opening the current tty w/o tty_mutex get_current_tty() gets a tty kref from the already kref'ed tty value of current->signal->tty while holding the sighand lock for the current task. This guarantees that the tty pointer returned from get_current_tty() points to a tty which remains referenceable while holding the kref. Although release_tty() may run concurrently, and thus the driver reference may be removed, release_one_tty() cannot have run, and won't while holding the tty kref. This, in turn, guarantees the tty_lock() can safely be acquired (since tty->magic and tty->legacy_mutex are still a valid dereferences). The tty_lock() also gets a tty kref to prevent the tty_unlock() from dereferencing a released tty. Thus, the kref returned from get_current_tty() can be released. Lastly, the first operation of tty_reopen() is to check the tty count. If non-zero, this ensures release_tty() is not running concurrently, and the driver references have not been removed. Reviewed-by: Alan Cox Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_io.c | 55 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 0eaf632ad39e..2e166a1be2b7 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -1935,20 +1935,20 @@ int tty_release(struct inode *inode, struct file *filp) } /** - * tty_open_current_tty - get tty of current task for open + * tty_open_current_tty - get locked tty of current task * @device: device number * @filp: file pointer to tty - * @return: tty of the current task iff @device is /dev/tty + * @return: locked tty of the current task iff @device is /dev/tty + * + * Performs a re-open of the current task's controlling tty. * * We cannot return driver and index like for the other nodes because * devpts will not work then. It expects inodes to be from devpts FS. - * - * We need to move to returning a refcounted object from all the lookup - * paths including this one. */ static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp) { struct tty_struct *tty; + int retval; if (device != MKDEV(TTYAUX_MAJOR, 0)) return NULL; @@ -1959,9 +1959,14 @@ static struct tty_struct *tty_open_current_tty(dev_t device, struct file *filp) filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */ /* noctty = 1; */ - tty_kref_put(tty); - /* FIXME: we put a reference and return a TTY! */ - /* This is only safe because the caller holds tty_mutex */ + tty_lock(tty); + tty_kref_put(tty); /* safe to drop the kref now */ + + retval = tty_reopen(tty); + if (retval < 0) { + tty_unlock(tty); + tty = ERR_PTR(retval); + } return tty; } @@ -2059,13 +2064,9 @@ static int tty_open(struct inode *inode, struct file *filp) index = -1; retval = 0; - mutex_lock(&tty_mutex); - /* This is protected by the tty_mutex */ tty = tty_open_current_tty(device, filp); - if (IS_ERR(tty)) { - retval = PTR_ERR(tty); - goto err_unlock; - } else if (!tty) { + if (!tty) { + mutex_lock(&tty_mutex); driver = tty_lookup_driver(device, filp, &noctty, &index); if (IS_ERR(driver)) { retval = PTR_ERR(driver); @@ -2078,21 +2079,21 @@ static int tty_open(struct inode *inode, struct file *filp) retval = PTR_ERR(tty); goto err_unlock; } + + if (tty) { + tty_lock(tty); + retval = tty_reopen(tty); + if (retval < 0) { + tty_unlock(tty); + tty = ERR_PTR(retval); + } + } else /* Returns with the tty_lock held for now */ + tty = tty_init_dev(driver, index); + + mutex_unlock(&tty_mutex); + tty_driver_kref_put(driver); } - if (tty) { - tty_lock(tty); - retval = tty_reopen(tty); - if (retval < 0) { - tty_unlock(tty); - tty = ERR_PTR(retval); - } - } else /* Returns with the tty_lock held for now */ - tty = tty_init_dev(driver, index); - - mutex_unlock(&tty_mutex); - if (driver) - tty_driver_kref_put(driver); if (IS_ERR(tty)) { retval = PTR_ERR(tty); goto err_file;