drivers/tty/tty_io.c | 52 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 9e930c009bf2..234b5eaeb3ff 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -1217,24 +1217,41 @@ static void tty_line_name(struct tty_driver *driver, int index, char *p) sprintf(p, "%s%d", driver->name, index + driver->name_base); } +static DEFINE_MUTEX(tty_lookup_mutex); + /** * tty_driver_lookup_tty() - find an existing tty, if any * @driver: the driver for the tty * @idx: the minor number * - * Return the tty, if found or ERR_PTR() otherwise. + * Return the tty if found, NULL if not, or ERR_PTR() for error. * - * Locking: tty_mutex must be held. If tty is found, the mutex must - * be held until the 'fast-open' is also done. Will change once we - * have refcounting in the driver and per driver locking + * Locking: tty_lookup_mutex, and refcount incremented if successfully found. */ static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver, struct inode *inode, int idx) { + struct tty_struct *tty; + + mutex_lock(&tty_lookup_mutex); if (driver->ops->lookup) - return driver->ops->lookup(driver, inode, idx); + tty = driver->ops->lookup(driver, inode, idx); + else + tty = driver->ttys[idx]; - return driver->ttys[idx]; + /* + * If the tty refcount is zero, this tty is in the process of being + * removed.. The 'tty_lookup_mutex' lock guarantees that the tty is + * still around, but without a refcount it may not be around once + * we drop the lock, so we must not return such a tty. + */ + if (tty && !IS_ERR(tty)) { + if (!atomic_inc_not_zero(&tty->kref.refcount)) + tty = NULL; + } + + mutex_unlock(&tty_lookup_mutex); + return tty; } /** @@ -1275,6 +1292,8 @@ int tty_standard_install(struct tty_driver *driver, struct tty_struct *tty) if (ret) return ret; + /* This must only ever be called through the driver install */ + WARN_ON_ONCE(!mutex_is_locked(&tty_lookup_mutex)); tty_driver_kref_get(driver); tty->count++; driver->ttys[tty->index] = tty; @@ -1297,8 +1316,13 @@ EXPORT_SYMBOL_GPL(tty_standard_install); static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty) { - return driver->ops->install ? driver->ops->install(driver, tty) : + int retval; + + mutex_lock(&tty_lookup_mutex); + retval = driver->ops->install ? driver->ops->install(driver, tty) : tty_standard_install(driver, tty); + mutex_unlock(&tty_lookup_mutex); + return retval; } /** @@ -1313,10 +1337,12 @@ static int tty_driver_install_tty(struct tty_driver *driver, */ void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *tty) { + mutex_lock(&tty_lookup_mutex); if (driver->ops->remove) driver->ops->remove(driver, tty); else driver->ttys[tty->index] = NULL; + mutex_unlock(&tty_lookup_mutex); } /* @@ -1809,9 +1835,6 @@ int tty_release(struct inode *inode, struct file *filp) * * 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) { @@ -1826,9 +1849,6 @@ 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 */ return tty; } @@ -1949,6 +1969,12 @@ retry_open: if (tty) { tty_lock(tty); + /* + * Now that we've locked the tty, we can drop the kref we got + * from tty_open_current_tty or tty_lookup_driver(). The lock + * keeps an extra refcount to it. + */ + tty_kref_put(tty); retval = tty_reopen(tty); if (retval < 0) { tty_unlock(tty);