diff options
Diffstat (limited to 'drivers/tty/tty_io.c')
| -rw-r--r-- | drivers/tty/tty_io.c | 3296 |
1 files changed, 1681 insertions, 1615 deletions
diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 366af832794b..e2d92cf70eb7 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 1991, 1992 Linus Torvalds */ @@ -32,7 +33,7 @@ * -- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993. * * Rewrote canonical mode and added more termios flags. - * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94 + * -- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94 * * Reorganized FASYNC support so mouse code can share it. * -- ctm@ardi.com, 9Sep95 @@ -69,7 +70,8 @@ #include <linux/errno.h> #include <linux/signal.h> #include <linux/fcntl.h> -#include <linux/sched.h> +#include <linux/sched/signal.h> +#include <linux/sched/task.h> #include <linux/interrupt.h> #include <linux/tty.h> #include <linux/tty_driver.h> @@ -85,6 +87,7 @@ #include <linux/string.h> #include <linux/slab.h> #include <linux/poll.h> +#include <linux/ppp-ioctl.h> #include <linux/proc_fs.h> #include <linux/init.h> #include <linux/module.h> @@ -95,8 +98,10 @@ #include <linux/seq_file.h> #include <linux/serial.h> #include <linux/ratelimit.h> - +#include <linux/compat.h> #include <linux/uaccess.h> +#include <linux/termios_internal.h> +#include <linux/fs.h> #include <linux/kbd_kern.h> #include <linux/vt_kern.h> @@ -104,8 +109,14 @@ #include <linux/kmod.h> #include <linux/nsproxy.h> +#include "tty.h" #undef TTY_DEBUG_HANGUP +#ifdef TTY_DEBUG_HANGUP +# define tty_debug_hangup(tty, f, args...) tty_debug(tty, f, ##args) +#else +# define tty_debug_hangup(tty, f, args...) do { } while (0) +#endif #define TTY_PARANOIA_CHECK 1 #define CHECK_TTY_COUNT 1 @@ -118,32 +129,25 @@ struct ktermios tty_std_termios = { /* for the benefit of tty drivers */ ECHOCTL | ECHOKE | IEXTEN, .c_cc = INIT_C_CC, .c_ispeed = 38400, - .c_ospeed = 38400 + .c_ospeed = 38400, + /* .c_line = N_TTY, */ }; - EXPORT_SYMBOL(tty_std_termios); /* This list gets poked at by procfs and various bits of boot up code. This - could do with some rationalisation such as pulling the tty proc function - into this file */ + * could do with some rationalisation such as pulling the tty proc function + * into this file. + */ LIST_HEAD(tty_drivers); /* linked list of tty drivers */ -/* Mutex to protect creating and releasing a tty. This is shared with - vt.c for deeply disgusting hack reasons */ +/* Mutex to protect creating and releasing a tty */ DEFINE_MUTEX(tty_mutex); -EXPORT_SYMBOL(tty_mutex); -/* Spinlock to protect the tty->tty_files list */ -DEFINE_SPINLOCK(tty_files_lock); - -static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *); -static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *); -ssize_t redirected_tty_write(struct file *, const char __user *, - size_t, loff_t *); -static unsigned int tty_poll(struct file *, poll_table *); +static ssize_t tty_read(struct kiocb *, struct iov_iter *); +static ssize_t tty_write(struct kiocb *, struct iov_iter *); +static __poll_t tty_poll(struct file *, poll_table *); static int tty_open(struct inode *, struct file *); -long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg); #ifdef CONFIG_COMPAT static long tty_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg); @@ -153,40 +157,20 @@ static long tty_compat_ioctl(struct file *file, unsigned int cmd, static int __tty_fasync(int fd, struct file *filp, int on); static int tty_fasync(int fd, struct file *filp, int on); static void release_tty(struct tty_struct *tty, int idx); -static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); -static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty); - -/** - * alloc_tty_struct - allocate a tty object - * - * Return a new empty tty structure. The data fields have not - * been initialized in any way but has been zeroed - * - * Locking: none - */ - -struct tty_struct *alloc_tty_struct(void) -{ - return kzalloc(sizeof(struct tty_struct), GFP_KERNEL); -} /** - * free_tty_struct - free a disused tty - * @tty: tty struct to free + * free_tty_struct - free a disused tty + * @tty: tty struct to free * - * Free the write buffers, tty queue and tty memory itself. + * Free the write buffers, tty queue and tty memory itself. * - * Locking: none. Must be called after tty is definitely unused + * Locking: none. Must be called after tty is definitely unused */ - -void free_tty_struct(struct tty_struct *tty) +static void free_tty_struct(struct tty_struct *tty) { - if (!tty) - return; - if (tty->dev) - put_device(tty->dev); - kfree(tty->write_buf); - tty->magic = 0xDEADDEAD; + tty_ldisc_deinit(tty); + put_device(tty->dev); + kvfree(tty->write_buf); kfree(tty); } @@ -216,13 +200,14 @@ void tty_add_file(struct tty_struct *tty, struct file *file) priv->tty = tty; priv->file = file; - spin_lock(&tty_files_lock); + spin_lock(&tty->files_lock); list_add(&priv->list, &tty->tty_files); - spin_unlock(&tty_files_lock); + spin_unlock(&tty->files_lock); } /** * tty_free_file - free file->private_data + * @file: to free private_data of * * This shall be used only for fail path handling when tty_add_file was not * called yet. @@ -239,51 +224,44 @@ void tty_free_file(struct file *file) static void tty_del_file(struct file *file) { struct tty_file_private *priv = file->private_data; + struct tty_struct *tty = priv->tty; - spin_lock(&tty_files_lock); + spin_lock(&tty->files_lock); list_del(&priv->list); - spin_unlock(&tty_files_lock); + spin_unlock(&tty->files_lock); tty_free_file(file); } - -#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base) - /** - * tty_name - return tty naming - * @tty: tty structure - * @buf: buffer for output + * tty_name - return tty naming + * @tty: tty structure * - * Convert a tty structure into a name. The name reflects the kernel - * naming policy and if udev is in use may not reflect user space + * Convert a tty structure into a name. The name reflects the kernel naming + * policy and if udev is in use may not reflect user space * - * Locking: none + * Locking: none */ - -char *tty_name(struct tty_struct *tty, char *buf) +const char *tty_name(const struct tty_struct *tty) { if (!tty) /* Hmm. NULL pointer. That's fun. */ - strcpy(buf, "NULL tty"); - else - strcpy(buf, tty->name); - return buf; + return "NULL tty"; + return tty->name; } - EXPORT_SYMBOL(tty_name); -int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, +const char *tty_driver_name(const struct tty_struct *tty) +{ + if (!tty || !tty->driver) + return ""; + return tty->driver->name; +} + +static int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, const char *routine) { #ifdef TTY_PARANOIA_CHECK if (!tty) { - printk(KERN_WARNING - "null TTY for (%d:%d) in %s\n", - imajor(inode), iminor(inode), routine); - return 1; - } - if (tty->magic != TTY_MAGIC) { - printk(KERN_WARNING - "bad magic number for tty struct (%d:%d) in %s\n", + pr_warn("(%d:%d): %s: NULL tty\n", imajor(inode), iminor(inode), routine); return 1; } @@ -291,48 +269,47 @@ int tty_paranoia_check(struct tty_struct *tty, struct inode *inode, return 0; } -static int check_tty_count(struct tty_struct *tty, const char *routine) +/* Caller must hold tty_lock */ +static void check_tty_count(struct tty_struct *tty, const char *routine) { #ifdef CHECK_TTY_COUNT struct list_head *p; - int count = 0; + int count = 0, kopen_count = 0; + + scoped_guard(spinlock, &tty->files_lock) + list_for_each(p, &tty->tty_files) + count++; - spin_lock(&tty_files_lock); - list_for_each(p, &tty->tty_files) { - count++; - } - spin_unlock(&tty_files_lock); if (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_SLAVE && tty->link && tty->link->count) count++; - if (tty->count != count) { - printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) " - "!= #fd's(%d) in %s\n", - tty->name, tty->count, count, routine); - return count; + if (tty_port_kopened(tty->port)) + kopen_count++; + if (tty->count != (count + kopen_count)) { + tty_warn(tty, "%s: tty->count(%d) != (#fd's(%d) + #kopen's(%d))\n", + routine, tty->count, count, kopen_count); } #endif - return 0; } /** - * get_tty_driver - find device of a tty - * @dev_t: device identifier - * @index: returns the index of the tty + * get_tty_driver - find device of a tty + * @device: device identifier + * @index: returns the index of the tty * - * This routine returns a tty driver structure, given a device number - * and also passes back the index number. + * This routine returns a tty driver structure, given a device number and also + * passes back the index number. * - * Locking: caller must hold tty_mutex + * Locking: caller must hold tty_mutex */ - static struct tty_driver *get_tty_driver(dev_t device, int *index) { struct tty_driver *p; list_for_each_entry(p, &tty_drivers, tty_drivers) { dev_t base = MKDEV(p->major, p->minor_start); + if (device < base || device >= base + p->num) continue; *index = device - base; @@ -341,20 +318,66 @@ static struct tty_driver *get_tty_driver(dev_t device, int *index) return NULL; } +/** + * tty_dev_name_to_number - return dev_t for device name + * @name: user space name of device under /dev + * @number: pointer to dev_t that this function will populate + * + * This function converts device names like ttyS0 or ttyUSB1 into dev_t like + * (4, 64) or (188, 1). If no corresponding driver is registered then the + * function returns -%ENODEV. + * + * Locking: this acquires tty_mutex to protect the tty_drivers list from + * being modified while we are traversing it, and makes sure to + * release it before exiting. + */ +int tty_dev_name_to_number(const char *name, dev_t *number) +{ + struct tty_driver *p; + int ret; + int index, prefix_length = 0; + const char *str; + + for (str = name; *str && !isdigit(*str); str++) + ; + + if (!*str) + return -EINVAL; + + ret = kstrtoint(str, 10, &index); + if (ret) + return ret; + + prefix_length = str - name; + + guard(mutex)(&tty_mutex); + + list_for_each_entry(p, &tty_drivers, tty_drivers) + if (prefix_length == strlen(p->name) && strncmp(name, + p->name, prefix_length) == 0) { + if (index < p->num) { + *number = MKDEV(p->major, p->minor_start + index); + return 0; + } + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(tty_dev_name_to_number); + #ifdef CONFIG_CONSOLE_POLL /** - * tty_find_polling_driver - find device of a polled tty - * @name: name string to match - * @line: pointer to resulting tty line nr + * tty_find_polling_driver - find device of a polled tty + * @name: name string to match + * @line: pointer to resulting tty line nr * - * This routine returns a tty driver structure, given a name - * and the condition that the tty driver is capable of polled - * operation. + * This routine returns a tty driver structure, given a name and the condition + * that the tty driver is capable of polled operation. */ struct tty_driver *tty_find_polling_driver(char *name, int *line) { - struct tty_driver *p, *res = NULL; + struct tty_driver *p; int tty_line = 0; int len; char *str, *stp; @@ -368,10 +391,11 @@ struct tty_driver *tty_find_polling_driver(char *name, int *line) len = str - name; tty_line = simple_strtoul(str, &str, 10); - mutex_lock(&tty_mutex); + guard(mutex)(&tty_mutex); + /* Search through the tty devices to look for a match */ list_for_each_entry(p, &tty_drivers, tty_drivers) { - if (strncmp(name, p->name, len) != 0) + if (!len || strncmp(name, p->name, len) != 0) continue; stp = str; if (*stp == ',') @@ -381,80 +405,30 @@ struct tty_driver *tty_find_polling_driver(char *name, int *line) if (tty_line >= 0 && tty_line < p->num && p->ops && p->ops->poll_init && !p->ops->poll_init(p, tty_line, stp)) { - res = tty_driver_kref_get(p); *line = tty_line; - break; + return tty_driver_kref_get(p); } } - mutex_unlock(&tty_mutex); - return res; + return NULL; } EXPORT_SYMBOL_GPL(tty_find_polling_driver); #endif -/** - * tty_check_change - check for POSIX terminal changes - * @tty: tty to check - * - * If we try to write to, or set the state of, a terminal and we're - * not in the foreground, send a SIGTTOU. If the signal is blocked or - * ignored, go ahead and perform the operation. (POSIX 7.2) - * - * Locking: ctrl_lock - */ - -int tty_check_change(struct tty_struct *tty) -{ - unsigned long flags; - int ret = 0; - - if (current->signal->tty != tty) - return 0; - - spin_lock_irqsave(&tty->ctrl_lock, flags); - - if (!tty->pgrp) { - printk(KERN_WARNING "tty_check_change: tty->pgrp == NULL!\n"); - goto out_unlock; - } - if (task_pgrp(current) == tty->pgrp) - goto out_unlock; - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - if (is_ignored(SIGTTOU)) - goto out; - if (is_current_pgrp_orphaned()) { - ret = -EIO; - goto out; - } - kill_pgrp(task_pgrp(current), SIGTTOU, 1); - set_thread_flag(TIF_SIGPENDING); - ret = -ERESTARTSYS; -out: - return ret; -out_unlock: - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - return ret; -} - -EXPORT_SYMBOL(tty_check_change); - -static ssize_t hung_up_tty_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos) +static ssize_t hung_up_tty_read(struct kiocb *iocb, struct iov_iter *to) { return 0; } -static ssize_t hung_up_tty_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static ssize_t hung_up_tty_write(struct kiocb *iocb, struct iov_iter *from) { return -EIO; } /* No kernel lock held - none needed ;) */ -static unsigned int hung_up_tty_poll(struct file *filp, poll_table *wait) +static __poll_t hung_up_tty_poll(struct file *filp, poll_table *wait) { - return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM; + return EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP | EPOLLRDNORM | EPOLLWRNORM; } static long hung_up_tty_ioctl(struct file *file, unsigned int cmd, @@ -469,22 +443,38 @@ static long hung_up_tty_compat_ioctl(struct file *file, return cmd == TIOCSPGRP ? -ENOTTY : -EIO; } +static int hung_up_tty_fasync(int fd, struct file *file, int on) +{ + return -ENOTTY; +} + +static void tty_show_fdinfo(struct seq_file *m, struct file *file) +{ + struct tty_struct *tty = file_tty(file); + + if (tty && tty->ops && tty->ops->show_fdinfo) + tty->ops->show_fdinfo(tty, m); +} + static const struct file_operations tty_fops = { - .llseek = no_llseek, - .read = tty_read, - .write = tty_write, + .read_iter = tty_read, + .write_iter = tty_write, + .splice_read = copy_splice_read, + .splice_write = iter_file_splice_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, .open = tty_open, .release = tty_release, .fasync = tty_fasync, + .show_fdinfo = tty_show_fdinfo, }; static const struct file_operations console_fops = { - .llseek = no_llseek, - .read = tty_read, - .write = redirected_tty_write, + .read_iter = tty_read, + .write_iter = redirected_tty_write, + .splice_read = copy_splice_read, + .splice_write = iter_file_splice_write, .poll = tty_poll, .unlocked_ioctl = tty_ioctl, .compat_ioctl = tty_compat_ioctl, @@ -494,27 +484,26 @@ static const struct file_operations console_fops = { }; static const struct file_operations hung_up_tty_fops = { - .llseek = no_llseek, - .read = hung_up_tty_read, - .write = hung_up_tty_write, + .read_iter = hung_up_tty_read, + .write_iter = hung_up_tty_write, .poll = hung_up_tty_poll, .unlocked_ioctl = hung_up_tty_ioctl, .compat_ioctl = hung_up_tty_compat_ioctl, .release = tty_release, + .fasync = hung_up_tty_fasync, }; static DEFINE_SPINLOCK(redirect_lock); static struct file *redirect; /** - * tty_wakeup - request more data - * @tty: terminal + * tty_wakeup - request more data + * @tty: terminal * - * Internal and external helper for wakeups of tty. This function - * informs the line discipline if present that the driver is ready - * to receive more output data. + * Internal and external helper for wakeups of tty. This function informs the + * line discipline if present that the driver is ready to receive more output + * data. */ - void tty_wakeup(struct tty_struct *tty) { struct tty_ldisc *ld; @@ -527,91 +516,59 @@ void tty_wakeup(struct tty_struct *tty) tty_ldisc_deref(ld); } } - wake_up_interruptible_poll(&tty->write_wait, POLLOUT); + wake_up_interruptible_poll(&tty->write_wait, EPOLLOUT); } - EXPORT_SYMBOL_GPL(tty_wakeup); /** - * tty_signal_session_leader - sends SIGHUP to session leader - * @tty controlling tty - * @exit_session if non-zero, signal all foreground group processes + * tty_release_redirect - Release a redirect on a pty if present + * @tty: tty device * - * Send SIGHUP and SIGCONT to the session leader and its process group. - * Optionally, signal all processes in the foreground process group. - * - * Returns the number of processes in the session with this tty - * as their controlling terminal. This value is used to drop - * tty references for those processes. + * This is available to the pty code so if the master closes, if the slave is a + * redirect it can release the redirect. */ -static int tty_signal_session_leader(struct tty_struct *tty, int exit_session) +static struct file *tty_release_redirect(struct tty_struct *tty) { - struct task_struct *p; - int refs = 0; - struct pid *tty_pgrp = NULL; - - read_lock(&tasklist_lock); - if (tty->session) { - do_each_pid_task(tty->session, PIDTYPE_SID, p) { - spin_lock_irq(&p->sighand->siglock); - if (p->signal->tty == tty) { - p->signal->tty = NULL; - /* We defer the dereferences outside fo - the tasklist lock */ - refs++; - } - if (!p->signal->leader) { - spin_unlock_irq(&p->sighand->siglock); - continue; - } - __group_send_sig_info(SIGHUP, SEND_SIG_PRIV, p); - __group_send_sig_info(SIGCONT, SEND_SIG_PRIV, p); - put_pid(p->signal->tty_old_pgrp); /* A noop */ - spin_lock(&tty->ctrl_lock); - tty_pgrp = get_pid(tty->pgrp); - if (tty->pgrp) - p->signal->tty_old_pgrp = get_pid(tty->pgrp); - spin_unlock(&tty->ctrl_lock); - spin_unlock_irq(&p->sighand->siglock); - } while_each_pid_task(tty->session, PIDTYPE_SID, p); - } - read_unlock(&tasklist_lock); + guard(spinlock)(&redirect_lock); - if (tty_pgrp) { - if (exit_session) - kill_pgrp(tty_pgrp, SIGHUP, exit_session); - put_pid(tty_pgrp); + if (redirect && file_tty(redirect) == tty) { + struct file *f = redirect; + redirect = NULL; + return f; } - return refs; + return NULL; } /** - * __tty_hangup - actual handler for hangup events - * @work: tty device + * __tty_hangup - actual handler for hangup events + * @tty: tty device + * @exit_session: if non-zero, signal all foreground group processes + * + * This can be called by a "kworker" kernel thread. That is process synchronous + * but doesn't hold any locks, so we need to make sure we have the appropriate + * locks for what we're doing. * - * This can be called by a "kworker" kernel thread. That is process - * synchronous but doesn't hold any locks, so we need to make sure we - * have the appropriate locks for what we're doing. + * The hangup event clears any pending redirections onto the hung up device. It + * ensures future writes will error and it does the needed line discipline + * hangup and signal delivery. The tty object itself remains intact. * - * The hangup event clears any pending redirections onto the hung up - * device. It ensures future writes will error and it does the needed - * line discipline hangup and signal delivery. The tty object itself - * remains intact. + * Locking: + * * BTM + * + * * redirect lock for undoing redirection + * * file list lock for manipulating list of ttys + * * tty_ldiscs_lock from called functions + * * termios_rwsem resetting termios data + * * tasklist_lock to walk task list for hangup event + * + * * ->siglock to protect ->signal/->sighand * - * Locking: - * BTM - * redirect lock for undoing redirection - * file list lock for manipulating list of ttys - * tty_ldisc_lock from called functions - * termios_mutex resetting termios data - * tasklist_lock to walk task list for hangup event - * ->siglock to protect ->signal/->sighand */ static void __tty_hangup(struct tty_struct *tty, int exit_session) { struct file *cons_filp = NULL; - struct file *filp, *f = NULL; + struct file *filp, *f; struct tty_file_private *priv; int closecount = 0, n; int refs; @@ -619,59 +576,59 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session) if (!tty) return; - - spin_lock(&redirect_lock); - if (redirect && file_tty(redirect) == tty) { - f = redirect; - redirect = NULL; - } - spin_unlock(&redirect_lock); + f = tty_release_redirect(tty); tty_lock(tty); - /* some functions below drop BTM, so we need this bit */ + if (test_bit(TTY_HUPPED, &tty->flags)) { + tty_unlock(tty); + return; + } + + /* + * Some console devices aren't actually hung up for technical and + * historical reasons, which can lead to indefinite interruptible + * sleep in n_tty_read(). The following explicitly tells + * n_tty_read() to abort readers. + */ set_bit(TTY_HUPPING, &tty->flags); /* inuse_filps is protected by the single tty lock, - this really needs to change if we want to flush the - workqueue with the lock held */ + * this really needs to change if we want to flush the + * workqueue with the lock held. + */ check_tty_count(tty, "tty_hangup"); - spin_lock(&tty_files_lock); + spin_lock(&tty->files_lock); /* This breaks for file handles being sent over AF_UNIX sockets ? */ list_for_each_entry(priv, &tty->tty_files, list) { filp = priv->file; - if (filp->f_op->write == redirected_tty_write) + if (filp->f_op->write_iter == redirected_tty_write) cons_filp = filp; - if (filp->f_op->write != tty_write) + if (filp->f_op->write_iter != tty_write) continue; closecount++; __tty_fasync(-1, filp, 0); /* can't block */ filp->f_op = &hung_up_tty_fops; } - spin_unlock(&tty_files_lock); + spin_unlock(&tty->files_lock); refs = tty_signal_session_leader(tty, exit_session); /* Account for the p->signal references we killed */ while (refs--) tty_kref_put(tty); - /* - * it drops BTM and thus races with reopen - * we protect the race by TTY_HUPPING - */ - tty_ldisc_hangup(tty); + tty_ldisc_hangup(tty, cons_filp != NULL); - spin_lock_irq(&tty->ctrl_lock); + spin_lock_irq(&tty->ctrl.lock); clear_bit(TTY_THROTTLED, &tty->flags); - clear_bit(TTY_PUSH, &tty->flags); clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); - put_pid(tty->session); - put_pid(tty->pgrp); - tty->session = NULL; - tty->pgrp = NULL; - tty->ctrl_status = 0; - spin_unlock_irq(&tty->ctrl_lock); + put_pid(tty->ctrl.session); + put_pid(tty->ctrl.pgrp); + tty->ctrl.session = NULL; + tty->ctrl.pgrp = NULL; + tty->ctrl.pktstatus = 0; + spin_unlock_irq(&tty->ctrl.lock); /* * If one of the devices matches a console pointer, we @@ -684,16 +641,14 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session) for (n = 0; n < closecount; n++) tty->ops->close(tty, cons_filp); } else if (tty->ops->hangup) - (tty->ops->hangup)(tty); + tty->ops->hangup(tty); /* - * We don't want to have driver/ldisc interactions beyond - * the ones we did here. The driver layer expects no - * calls after ->hangup() from the ldisc side. However we - * can't yet guarantee all that. + * We don't want to have driver/ldisc interactions beyond the ones + * we did here. The driver layer expects no calls after ->hangup() + * from the ldisc side, which is now guaranteed. */ set_bit(TTY_HUPPED, &tty->flags); clear_bit(TTY_HUPPING, &tty->flags); - tty_unlock(tty); if (f) @@ -709,52 +664,40 @@ static void do_tty_hangup(struct work_struct *work) } /** - * tty_hangup - trigger a hangup event - * @tty: tty to hangup + * tty_hangup - trigger a hangup event + * @tty: tty to hangup * - * A carrier loss (virtual or otherwise) has occurred on this like - * schedule a hangup sequence to run after this event. + * A carrier loss (virtual or otherwise) has occurred on @tty. Schedule a + * hangup sequence to run after this event. */ - void tty_hangup(struct tty_struct *tty) { -#ifdef TTY_DEBUG_HANGUP - char buf[64]; - printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf)); -#endif + tty_debug_hangup(tty, "hangup\n"); schedule_work(&tty->hangup_work); } - EXPORT_SYMBOL(tty_hangup); /** - * tty_vhangup - process vhangup - * @tty: tty to hangup + * tty_vhangup - process vhangup + * @tty: tty to hangup * - * The user has asked via system call for the terminal to be hung up. - * We do this synchronously so that when the syscall returns the process - * is complete. That guarantee is necessary for security reasons. + * The user has asked via system call for the terminal to be hung up. We do + * this synchronously so that when the syscall returns the process is complete. + * That guarantee is necessary for security reasons. */ - void tty_vhangup(struct tty_struct *tty) { -#ifdef TTY_DEBUG_HANGUP - char buf[64]; - - printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf)); -#endif + tty_debug_hangup(tty, "vhangup\n"); __tty_hangup(tty, 0); } - EXPORT_SYMBOL(tty_vhangup); /** - * tty_vhangup_self - process vhangup for own ctty + * tty_vhangup_self - process vhangup for own ctty * - * Perform a vhangup on the current controlling tty + * Perform a vhangup on the current controlling tty */ - void tty_vhangup_self(void) { struct tty_struct *tty; @@ -767,287 +710,231 @@ void tty_vhangup_self(void) } /** - * tty_vhangup_session - hangup session leader exit - * @tty: tty to hangup + * tty_vhangup_session - hangup session leader exit + * @tty: tty to hangup * - * The session leader is exiting and hanging up its controlling terminal. - * Every process in the foreground process group is signalled SIGHUP. + * The session leader is exiting and hanging up its controlling terminal. + * Every process in the foreground process group is signalled %SIGHUP. * - * We do this synchronously so that when the syscall returns the process - * is complete. That guarantee is necessary for security reasons. + * We do this synchronously so that when the syscall returns the process is + * complete. That guarantee is necessary for security reasons. */ - -static void tty_vhangup_session(struct tty_struct *tty) +void tty_vhangup_session(struct tty_struct *tty) { -#ifdef TTY_DEBUG_HANGUP - char buf[64]; - - printk(KERN_DEBUG "%s vhangup session...\n", tty_name(tty, buf)); -#endif + tty_debug_hangup(tty, "session hangup\n"); __tty_hangup(tty, 1); } /** - * tty_hung_up_p - was tty hung up - * @filp: file pointer of tty + * tty_hung_up_p - was tty hung up + * @filp: file pointer of tty * - * Return true if the tty has been subject to a vhangup or a carrier - * loss + * Return: true if the tty has been subject to a vhangup or a carrier loss */ - int tty_hung_up_p(struct file *filp) { - return (filp->f_op == &hung_up_tty_fops); + return (filp && filp->f_op == &hung_up_tty_fops); } - EXPORT_SYMBOL(tty_hung_up_p); -static void session_clear_tty(struct pid *session) +void __stop_tty(struct tty_struct *tty) { - struct task_struct *p; - do_each_pid_task(session, PIDTYPE_SID, p) { - proc_clear_tty(p); - } while_each_pid_task(session, PIDTYPE_SID, p); + if (tty->flow.stopped) + return; + tty->flow.stopped = true; + if (tty->ops->stop) + tty->ops->stop(tty); } /** - * disassociate_ctty - disconnect controlling tty - * @on_exit: true if exiting so need to "hang up" the session - * - * This function is typically called only by the session leader, when - * it wants to disassociate itself from its controlling tty. + * stop_tty - propagate flow control + * @tty: tty to stop * - * It performs the following functions: - * (1) Sends a SIGHUP and SIGCONT to the foreground process group - * (2) Clears the tty from being controlling the session - * (3) Clears the controlling tty for all processes in the - * session group. + * Perform flow control to the driver. May be called on an already stopped + * device and will not re-call the &tty_driver->stop() method. * - * The argument on_exit is set to 1 if called when a process is - * exiting; it is 0 if called by the ioctl TIOCNOTTY. + * This functionality is used by both the line disciplines for halting incoming + * flow and by the driver. It may therefore be called from any context, may be + * under the tty %atomic_write_lock but not always. * - * Locking: - * BTM is taken for hysterical raisins, and held when - * called from no_tty(). - * tty_mutex is taken to protect tty - * ->siglock is taken to protect ->signal/->sighand - * tasklist_lock is taken to walk process list for sessions - * ->siglock is taken to protect ->signal/->sighand + * Locking: + * flow.lock */ - -void disassociate_ctty(int on_exit) +void stop_tty(struct tty_struct *tty) { - struct tty_struct *tty; - - if (!current->signal->leader) - return; - - tty = get_current_tty(); - if (tty) { - if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY) { - tty_vhangup_session(tty); - } else { - struct pid *tty_pgrp = tty_get_pgrp(tty); - if (tty_pgrp) { - kill_pgrp(tty_pgrp, SIGHUP, on_exit); - kill_pgrp(tty_pgrp, SIGCONT, on_exit); - put_pid(tty_pgrp); - } - } - tty_kref_put(tty); + guard(spinlock_irqsave)(&tty->flow.lock); + __stop_tty(tty); +} +EXPORT_SYMBOL(stop_tty); - } else if (on_exit) { - struct pid *old_pgrp; - spin_lock_irq(¤t->sighand->siglock); - old_pgrp = current->signal->tty_old_pgrp; - current->signal->tty_old_pgrp = NULL; - spin_unlock_irq(¤t->sighand->siglock); - if (old_pgrp) { - kill_pgrp(old_pgrp, SIGHUP, on_exit); - kill_pgrp(old_pgrp, SIGCONT, on_exit); - put_pid(old_pgrp); - } +void __start_tty(struct tty_struct *tty) +{ + if (!tty->flow.stopped || tty->flow.tco_stopped) return; - } - - spin_lock_irq(¤t->sighand->siglock); - put_pid(current->signal->tty_old_pgrp); - current->signal->tty_old_pgrp = NULL; - spin_unlock_irq(¤t->sighand->siglock); - - tty = get_current_tty(); - if (tty) { - unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - put_pid(tty->session); - put_pid(tty->pgrp); - tty->session = NULL; - tty->pgrp = NULL; - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - tty_kref_put(tty); - } else { -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "error attempted to write to tty [0x%p]" - " = NULL", tty); -#endif - } - - /* Now clear signal->tty under the lock */ - read_lock(&tasklist_lock); - session_clear_tty(task_session(current)); - read_unlock(&tasklist_lock); + tty->flow.stopped = false; + if (tty->ops->start) + tty->ops->start(tty); + tty_wakeup(tty); } /** + * start_tty - propagate flow control + * @tty: tty to start * - * no_tty - Ensure the current process does not have a controlling tty + * Start a tty that has been stopped if at all possible. If @tty was previously + * stopped and is now being started, the &tty_driver->start() method is invoked + * and the line discipline woken. + * + * Locking: + * flow.lock */ -void no_tty(void) +void start_tty(struct tty_struct *tty) { - /* FIXME: Review locking here. The tty_lock never covered any race - between a new association and proc_clear_tty but possible we need - to protect against this anyway */ - struct task_struct *tsk = current; - disassociate_ctty(0); - proc_clear_tty(tsk); + guard(spinlock_irqsave)(&tty->flow.lock); + __start_tty(tty); } +EXPORT_SYMBOL(start_tty); +static void tty_update_time(struct tty_struct *tty, bool mtime) +{ + time64_t sec = ktime_get_real_seconds(); + struct tty_file_private *priv; -/** - * stop_tty - propagate flow control - * @tty: tty to stop - * - * Perform flow control to the driver. For PTY/TTY pairs we - * must also propagate the TIOCKPKT status. May be called - * on an already stopped device and will not re-call the driver - * method. - * - * This functionality is used by both the line disciplines for - * halting incoming flow and by the driver. It may therefore be - * called from any context, may be under the tty atomic_write_lock - * but not always. - * - * Locking: - * Uses the tty control lock internally - */ + guard(spinlock)(&tty->files_lock); -void stop_tty(struct tty_struct *tty) -{ - unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - if (tty->stopped) { - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - return; - } - tty->stopped = 1; - if (tty->link && tty->link->packet) { - tty->ctrl_status &= ~TIOCPKT_START; - tty->ctrl_status |= TIOCPKT_STOP; - wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); + list_for_each_entry(priv, &tty->tty_files, list) { + struct inode *inode = file_inode(priv->file); + struct timespec64 time = mtime ? inode_get_mtime(inode) : inode_get_atime(inode); + + /* + * We only care if the two values differ in anything other than the + * lower three bits (i.e every 8 seconds). If so, then we can update + * the time of the tty device, otherwise it could be construded as a + * security leak to let userspace know the exact timing of the tty. + */ + if ((sec ^ time.tv_sec) & ~7) { + if (mtime) + inode_set_mtime(inode, sec, 0); + else + inode_set_atime(inode, sec, 0); + } } - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - if (tty->ops->stop) - (tty->ops->stop)(tty); } -EXPORT_SYMBOL(stop_tty); - -/** - * start_tty - propagate flow control - * @tty: tty to start +/* + * Iterate on the ldisc ->read() function until we've gotten all + * the data the ldisc has for us. * - * Start a tty that has been stopped if at all possible. Perform - * any necessary wakeups and propagate the TIOCPKT status. If this - * is the tty was previous stopped and is being started then the - * driver start method is invoked and the line discipline woken. + * The "cookie" is something that the ldisc read function can fill + * in to let us know that there is more data to be had. * - * Locking: - * ctrl_lock + * We promise to continue to call the ldisc until it stops returning + * data or clears the cookie. The cookie may be something that the + * ldisc maintains state for and needs to free. */ - -void start_tty(struct tty_struct *tty) +static ssize_t iterate_tty_read(struct tty_ldisc *ld, struct tty_struct *tty, + struct file *file, struct iov_iter *to) { - unsigned long flags; - spin_lock_irqsave(&tty->ctrl_lock, flags); - if (!tty->stopped || tty->flow_stopped) { - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - return; - } - tty->stopped = 0; - if (tty->link && tty->link->packet) { - tty->ctrl_status &= ~TIOCPKT_STOP; - tty->ctrl_status |= TIOCPKT_START; - wake_up_interruptible_poll(&tty->link->read_wait, POLLIN); - } - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - if (tty->ops->start) - (tty->ops->start)(tty); - /* If we have a running line discipline it may need kicking */ - tty_wakeup(tty); -} + void *cookie = NULL; + unsigned long offset = 0; + ssize_t retval = 0; + size_t copied, count = iov_iter_count(to); + u8 kernel_buf[64]; -EXPORT_SYMBOL(start_tty); + do { + ssize_t size = min(count, sizeof(kernel_buf)); -/* We limit tty time update visibility to every 8 seconds or so. */ -static void tty_update_time(struct timespec *time) -{ - unsigned long sec = get_seconds() & ~7; - if ((long)(sec - time->tv_sec) > 0) - time->tv_sec = sec; + size = ld->ops->read(tty, file, kernel_buf, size, &cookie, offset); + if (!size) + break; + + if (size < 0) { + /* Did we have an earlier error (ie -EFAULT)? */ + if (retval) + break; + retval = size; + + /* + * -EOVERFLOW means we didn't have enough space + * for a whole packet, and we shouldn't return + * a partial result. + */ + if (retval == -EOVERFLOW) + offset = 0; + break; + } + + copied = copy_to_iter(kernel_buf, size, to); + offset += copied; + count -= copied; + + /* + * If the user copy failed, we still need to do another ->read() + * call if we had a cookie to let the ldisc clear up. + * + * But make sure size is zeroed. + */ + if (unlikely(copied != size)) { + count = 0; + retval = -EFAULT; + } + } while (cookie); + + /* We always clear tty buffer in case they contained passwords */ + memzero_explicit(kernel_buf, sizeof(kernel_buf)); + return offset ? offset : retval; } + /** - * tty_read - read method for tty device files - * @file: pointer to tty file - * @buf: user buffer - * @count: size of user buffer - * @ppos: unused + * tty_read - read method for tty device files + * @iocb: kernel I/O control block + * @to: destination for the data read * - * Perform the read system call function on this terminal device. Checks - * for hung up devices before calling the line discipline method. + * Perform the read system call function on this terminal device. Checks + * for hung up devices before calling the line discipline method. * - * Locking: - * Locks the line discipline internally while needed. Multiple - * read calls may be outstanding in parallel. + * Locking: + * Locks the line discipline internally while needed. Multiple read calls + * may be outstanding in parallel. */ - -static ssize_t tty_read(struct file *file, char __user *buf, size_t count, - loff_t *ppos) +static ssize_t tty_read(struct kiocb *iocb, struct iov_iter *to) { - int i; + struct file *file = iocb->ki_filp; struct inode *inode = file_inode(file); struct tty_struct *tty = file_tty(file); struct tty_ldisc *ld; + ssize_t ret; if (tty_paranoia_check(tty, inode, "tty_read")) return -EIO; - if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags))) + if (!tty || tty_io_error(tty)) return -EIO; /* We want to wait for the line discipline to sort out in this - situation */ + * situation. + */ ld = tty_ldisc_ref_wait(tty); + if (!ld) + return hung_up_tty_read(iocb, to); + ret = -EIO; if (ld->ops->read) - i = (ld->ops->read)(tty, file, buf, count); - else - i = -EIO; + ret = iterate_tty_read(ld, tty, file, to); tty_ldisc_deref(ld); - if (i > 0) - tty_update_time(&inode->i_atime); + if (ret > 0) + tty_update_time(tty, false); - return i; + return ret; } void tty_write_unlock(struct tty_struct *tty) - __releases(&tty->atomic_write_lock) { mutex_unlock(&tty->atomic_write_lock); - wake_up_interruptible_poll(&tty->write_wait, POLLOUT); + wake_up_interruptible_poll(&tty->write_wait, EPOLLOUT); } -int tty_write_lock(struct tty_struct *tty, int ndelay) - __acquires(&tty->atomic_write_lock) +int tty_write_lock(struct tty_struct *tty, bool ndelay) { if (!mutex_trylock(&tty->atomic_write_lock)) { if (ndelay) @@ -1062,15 +949,11 @@ int tty_write_lock(struct tty_struct *tty, int ndelay) * Split writes up in sane blocksizes to avoid * denial-of-service type attacks */ -static inline ssize_t do_tty_write( - ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t), - struct tty_struct *tty, - struct file *file, - const char __user *buf, - size_t count) +static ssize_t iterate_tty_write(struct tty_ldisc *ld, struct tty_struct *tty, + struct file *file, struct iov_iter *from) { + size_t chunk, count = iov_iter_count(from); ssize_t ret, written = 0; - unsigned int chunk; ret = tty_write_lock(tty, file->f_flags & O_NDELAY); if (ret < 0) @@ -1088,9 +971,6 @@ static inline ssize_t do_tty_write( * layer has problems with bigger chunks. It will * claim to be able to handle more characters than * it actually does. - * - * FIXME: This can probably go away now except that 64K chunks - * are too likely to fail unless switched to vmalloc... */ chunk = 2048; if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags)) @@ -1100,34 +980,41 @@ static inline ssize_t do_tty_write( /* write_buf/write_cnt is protected by the atomic_write_lock mutex */ if (tty->write_cnt < chunk) { - unsigned char *buf_chunk; + u8 *buf_chunk; if (chunk < 1024) chunk = 1024; - buf_chunk = kmalloc(chunk, GFP_KERNEL); + buf_chunk = kvmalloc(chunk, GFP_KERNEL | __GFP_RETRY_MAYFAIL); if (!buf_chunk) { ret = -ENOMEM; goto out; } - kfree(tty->write_buf); + kvfree(tty->write_buf); tty->write_cnt = chunk; tty->write_buf = buf_chunk; } /* Do the write .. */ for (;;) { - size_t size = count; - if (size > chunk) - size = chunk; + size_t size = min(chunk, count); + ret = -EFAULT; - if (copy_from_user(tty->write_buf, buf, size)) + if (copy_from_iter(tty->write_buf, size, from) != size) break; - ret = write(tty, file, tty->write_buf, size); + + ret = ld->ops->write(tty, file, tty->write_buf, size); if (ret <= 0) break; + written += ret; - buf += ret; + if (ret > size) + break; + + /* FIXME! Have Al check this! */ + if (ret != size) + iov_iter_revert(from, size-ret); + count -= ret; if (!count) break; @@ -1137,7 +1024,7 @@ static inline ssize_t do_tty_write( cond_resched(); } if (written) { - tty_update_time(&file_inode(file)->i_mtime); + tty_update_time(tty, true); ret = written; } out: @@ -1145,77 +1032,77 @@ out: return ret; } +#ifdef CONFIG_PRINT_QUOTA_WARNING /** * tty_write_message - write a message to a certain tty, not just the console. * @tty: the destination tty_struct * @msg: the message to write * - * This is used for messages that need to be redirected to a specific tty. - * We don't put it into the syslog queue right now maybe in the future if - * really needed. + * This is used for messages that need to be redirected to a specific tty. We + * don't put it into the syslog queue right now maybe in the future if really + * needed. * * We must still hold the BTM and test the CLOSING flag for the moment. + * + * This function is DEPRECATED, do not use in new code. */ - void tty_write_message(struct tty_struct *tty, char *msg) { if (tty) { mutex_lock(&tty->atomic_write_lock); tty_lock(tty); - if (tty->ops->write && !test_bit(TTY_CLOSING, &tty->flags)) { - tty_unlock(tty); + if (tty->ops->write && tty->count > 0) tty->ops->write(tty, msg, strlen(msg)); - } else - tty_unlock(tty); + tty_unlock(tty); tty_write_unlock(tty); } - return; } +#endif - -/** - * tty_write - write method for tty device file - * @file: tty file pointer - * @buf: user data to write - * @count: bytes to write - * @ppos: unused - * - * Write data to a tty device via the line discipline. - * - * Locking: - * Locks the line discipline as required - * Writes to the tty driver are serialized by the atomic_write_lock - * and are then processed in chunks to the device. The line discipline - * write method will not be invoked in parallel for each device. - */ - -static ssize_t tty_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +static ssize_t file_tty_write(struct file *file, struct kiocb *iocb, struct iov_iter *from) { struct tty_struct *tty = file_tty(file); - struct tty_ldisc *ld; + struct tty_ldisc *ld; ssize_t ret; if (tty_paranoia_check(tty, file_inode(file), "tty_write")) return -EIO; - if (!tty || !tty->ops->write || - (test_bit(TTY_IO_ERROR, &tty->flags))) - return -EIO; + if (!tty || !tty->ops->write || tty_io_error(tty)) + return -EIO; /* Short term debug to catch buggy drivers */ if (tty->ops->write_room == NULL) - printk(KERN_ERR "tty driver %s lacks a write_room method.\n", - tty->driver->name); + tty_err(tty, "missing write_room method\n"); ld = tty_ldisc_ref_wait(tty); + if (!ld) + return hung_up_tty_write(iocb, from); if (!ld->ops->write) ret = -EIO; else - ret = do_tty_write(ld->ops->write, tty, file, buf, count); + ret = iterate_tty_write(ld, tty, file, from); tty_ldisc_deref(ld); return ret; } -ssize_t redirected_tty_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) +/** + * tty_write - write method for tty device file + * @iocb: kernel I/O control block + * @from: iov_iter with data to write + * + * Write data to a tty device via the line discipline. + * + * Locking: + * Locks the line discipline as required + * Writes to the tty driver are serialized by the atomic_write_lock + * and are then processed in chunks to the device. The line + * discipline write method will not be invoked in parallel for + * each device. + */ +static ssize_t tty_write(struct kiocb *iocb, struct iov_iter *from) +{ + return file_tty_write(iocb->ki_filp, iocb, from); +} + +ssize_t redirected_tty_write(struct kiocb *iocb, struct iov_iter *iter) { struct file *p = NULL; @@ -1224,30 +1111,68 @@ ssize_t redirected_tty_write(struct file *file, const char __user *buf, p = get_file(redirect); spin_unlock(&redirect_lock); + /* + * We know the redirected tty is just another tty, we can + * call file_tty_write() directly with that file pointer. + */ if (p) { ssize_t res; - res = vfs_write(p, buf, count, &p->f_pos); + + res = file_tty_write(p, iocb, iter); fput(p); return res; } - return tty_write(file, buf, count, ppos); + return tty_write(iocb, iter); } -static char ptychar[] = "pqrstuvwxyzabcde"; +/** + * tty_send_xchar - send priority character + * @tty: the tty to send to + * @ch: xchar to send + * + * Send a high priority character to the tty even if stopped. + * + * Locking: none for xchar method, write ordering for write method. + */ +int tty_send_xchar(struct tty_struct *tty, u8 ch) +{ + bool was_stopped = tty->flow.stopped; + + if (tty->ops->send_xchar) { + down_read(&tty->termios_rwsem); + tty->ops->send_xchar(tty, ch); + up_read(&tty->termios_rwsem); + return 0; + } + + if (tty_write_lock(tty, false) < 0) + return -ERESTARTSYS; + + down_read(&tty->termios_rwsem); + if (was_stopped) + start_tty(tty); + tty->ops->write(tty, &ch, 1); + if (was_stopped) + stop_tty(tty); + up_read(&tty->termios_rwsem); + tty_write_unlock(tty); + return 0; +} /** - * pty_line_name - generate name for a pty - * @driver: the tty driver in use - * @index: the minor number - * @p: output buffer of at least 6 bytes + * pty_line_name - generate name for a pty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 6 bytes * - * Generate a name from a driver reference and write it to the output - * buffer. + * Generate a name from a @driver reference and write it to the output buffer + * @p. * - * Locking: None + * Locking: None */ static void pty_line_name(struct tty_driver *driver, int index, char *p) { + static const char ptychar[] = "pqrstuvwxyzabcde"; int i = index + driver->name_base; /* ->name is initialized to "ttyp", but "tty" is expected */ sprintf(p, "%s%c%x", @@ -1256,53 +1181,64 @@ static void pty_line_name(struct tty_driver *driver, int index, char *p) } /** - * tty_line_name - generate name for a tty - * @driver: the tty driver in use - * @index: the minor number - * @p: output buffer of at least 7 bytes + * tty_line_name - generate name for a tty + * @driver: the tty driver in use + * @index: the minor number + * @p: output buffer of at least 7 bytes * - * Generate a name from a driver reference and write it to the output - * buffer. + * Generate a name from a @driver reference and write it to the output buffer + * @p. * - * Locking: None + * Locking: None */ -static void tty_line_name(struct tty_driver *driver, int index, char *p) +static ssize_t tty_line_name(struct tty_driver *driver, int index, char *p) { if (driver->flags & TTY_DRIVER_UNNUMBERED_NODE) - strcpy(p, driver->name); + return sprintf(p, "%s", driver->name); else - sprintf(p, "%s%d", driver->name, index + driver->name_base); + return sprintf(p, "%s%d", driver->name, + index + driver->name_base); } /** - * tty_driver_lookup_tty() - find an existing tty, if any - * @driver: the driver for the tty - * @idx: the minor number + * tty_driver_lookup_tty() - find an existing tty, if any + * @driver: the driver for the tty + * @file: file object + * @idx: the minor number * - * Return the tty, if found or ERR_PTR() otherwise. + * Return: the tty, if found. If not found, return %NULL or ERR_PTR() if the + * driver lookup() method returns an 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_mutex must be held. If the tty is found, bump the tty kref. */ static struct tty_struct *tty_driver_lookup_tty(struct tty_driver *driver, - struct inode *inode, int idx) + struct file *file, int idx) { - if (driver->ops->lookup) - return driver->ops->lookup(driver, inode, idx); + struct tty_struct *tty; - return driver->ttys[idx]; + if (driver->ops->lookup) { + if (!file) + tty = ERR_PTR(-EIO); + else + tty = driver->ops->lookup(driver, file, idx); + } else { + if (idx >= driver->num) + return ERR_PTR(-EINVAL); + tty = driver->ttys[idx]; + } + if (!IS_ERR(tty)) + tty_kref_get(tty); + return tty; } /** - * tty_init_termios - helper for termios setup - * @tty: the tty to set up + * tty_init_termios - helper for termios setup + * @tty: the tty to set up * - * Initialise the termios structures for this tty. Thus runs under - * the tty_mutex currently so we can be relaxed about ordering. + * Initialise the termios structure for this tty. This runs under the + * %tty_mutex currently so we can be relaxed about ordering. */ - -int tty_init_termios(struct tty_struct *tty) +void tty_init_termios(struct tty_struct *tty) { struct ktermios *tp; int idx = tty->index; @@ -1312,24 +1248,29 @@ int tty_init_termios(struct tty_struct *tty) else { /* Check for lazy saved data */ tp = tty->driver->termios[idx]; - if (tp != NULL) + if (tp != NULL) { tty->termios = *tp; - else + tty->termios.c_line = tty->driver->init_termios.c_line; + } else tty->termios = tty->driver->init_termios; } /* Compatibility until drivers always set this */ tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios); tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios); - return 0; } EXPORT_SYMBOL_GPL(tty_init_termios); +/** + * tty_standard_install - usual tty->ops->install + * @driver: the driver for the tty + * @tty: the tty + * + * If the @driver overrides @tty->ops->install, it still can call this function + * to perform the standard install operations. + */ int tty_standard_install(struct tty_driver *driver, struct tty_struct *tty) { - int ret = tty_init_termios(tty); - if (ret) - return ret; - + tty_init_termios(tty); tty_driver_kref_get(driver); tty->count++; driver->ttys[tty->index] = tty; @@ -1338,16 +1279,15 @@ int tty_standard_install(struct tty_driver *driver, struct tty_struct *tty) EXPORT_SYMBOL_GPL(tty_standard_install); /** - * tty_driver_install_tty() - install a tty entry in the driver - * @driver: the driver for the tty - * @tty: the tty + * tty_driver_install_tty() - install a tty entry in the driver + * @driver: the driver for the tty + * @tty: the tty * - * Install a tty object into the driver tables. The tty->index field - * will be set by the time this is called. This method is responsible - * for ensuring any need additional structures are allocated and - * configured. + * Install a tty object into the driver tables. The @tty->index field will be + * set by the time this is called. This method is responsible for ensuring any + * need additional structures are allocated and configured. * - * Locking: tty_mutex for now + * Locking: tty_mutex for now */ static int tty_driver_install_tty(struct tty_driver *driver, struct tty_struct *tty) @@ -1357,16 +1297,16 @@ static int tty_driver_install_tty(struct tty_driver *driver, } /** - * tty_driver_remove_tty() - remove a tty from the driver tables - * @driver: the driver for the tty - * @idx: the minor number + * tty_driver_remove_tty() - remove a tty from the driver tables + * @driver: the driver for the tty + * @tty: tty to remove * - * Remvoe a tty object from the driver tables. The tty->index field - * will be set by the time this is called. + * Remove a tty object from the driver tables. The tty->index field will be set + * by the time this is called. * - * Locking: tty_mutex for now + * Locking: tty_mutex for now */ -void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *tty) +static void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *tty) { if (driver->ops->remove) driver->ops->remove(driver, tty); @@ -1374,66 +1314,73 @@ void tty_driver_remove_tty(struct tty_driver *driver, struct tty_struct *tty) driver->ttys[tty->index] = NULL; } -/* - * tty_reopen() - fast re-open of an open tty - * @tty - the tty to open +/** + * tty_reopen() - fast re-open of an open tty + * @tty: the tty to open * - * Return 0 on success, -errno on error. + * Re-opens on master ptys are not allowed and return -%EIO. * - * Locking: tty_mutex must be held from the time the tty was found - * till this open completes. + * Locking: Caller must hold tty_lock + * Return: 0 on success, -errno on error. */ static int tty_reopen(struct tty_struct *tty) { struct tty_driver *driver = tty->driver; + struct tty_ldisc *ld; + int retval = 0; - if (test_bit(TTY_CLOSING, &tty->flags) || - test_bit(TTY_HUPPING, &tty->flags) || - test_bit(TTY_LDISC_CHANGING, &tty->flags)) + if (driver->type == TTY_DRIVER_TYPE_PTY && + driver->subtype == PTY_TYPE_MASTER) return -EIO; - if (driver->type == TTY_DRIVER_TYPE_PTY && - driver->subtype == PTY_TYPE_MASTER) { - /* - * special case for PTY masters: only one open permitted, - * and the slave side open count is incremented as well. - */ - if (tty->count) - return -EIO; + if (!tty->count) + return -EAGAIN; + + if (test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN)) + return -EBUSY; - tty->link->count++; + ld = tty_ldisc_ref_wait(tty); + if (ld) { + tty_ldisc_deref(ld); + } else { + retval = tty_ldisc_lock(tty, 5 * HZ); + if (retval) + return retval; + + if (!tty->ldisc) + retval = tty_ldisc_reinit(tty, tty->termios.c_line); + tty_ldisc_unlock(tty); } - tty->count++; - WARN_ON(!test_bit(TTY_LDISC, &tty->flags)); + if (retval == 0) + tty->count++; - return 0; + return retval; } /** - * tty_init_dev - initialise a tty device - * @driver: tty driver we are opening a device on - * @idx: device index - * @ret_tty: returned tty structure + * tty_init_dev - initialise a tty device + * @driver: tty driver we are opening a device on + * @idx: device index + * + * Prepare a tty device. This may not be a "new" clean device but could also be + * an active device. The pty drivers require special handling because of this. * - * Prepare a tty device. This may not be a "new" clean device but - * could also be an active device. The pty drivers require special - * handling because of this. + * Locking: + * The function is called under the tty_mutex, which protects us from the + * tty struct or driver itself going away. * - * Locking: - * The function is called under the tty_mutex, which - * protects us from the tty struct or driver itself going away. + * On exit the tty device has the line discipline attached and a reference + * count of 1. If a pair was created for pty/tty use and the other was a pty + * master then it too has a reference count of 1. * - * On exit the tty device has the line discipline attached and - * a reference count of 1. If a pair was created for pty/tty use - * and the other was a pty master then it too has a reference count of 1. + * WSH 06/09/97: Rewritten to remove races and properly clean up after a failed + * open. The new code protects the open with a mutex, so it's really quite + * straightforward. The mutex locking can probably be relaxed for the (most + * common) case of reopening a tty. * - * WSH 06/09/97: Rewritten to remove races and properly clean up after a - * failed open. The new code protects the open with a mutex, so it's - * really quite straightforward. The mutex locking can probably be - * relaxed for the (most common) case of reopening a tty. + * Return: new tty structure */ - struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) { struct tty_struct *tty; @@ -1444,31 +1391,36 @@ struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) * This code guarantees that either everything succeeds and the * TTY is ready for operation, or else the table slots are vacated * and the allocated memory released. (Except that the termios - * and locked termios may be retained.) + * may be retained.) */ if (!try_module_get(driver->owner)) return ERR_PTR(-ENODEV); - tty = alloc_tty_struct(); + tty = alloc_tty_struct(driver, idx); if (!tty) { retval = -ENOMEM; goto err_module_put; } - initialize_tty_struct(tty, driver, idx); tty_lock(tty); retval = tty_driver_install_tty(driver, tty); if (retval < 0) - goto err_deinit_tty; + goto err_free_tty; if (!tty->port) tty->port = driver->ports[idx]; - WARN_RATELIMIT(!tty->port, - "%s: %s driver does not set tty->port. This will crash the kernel later. Fix the driver!\n", - __func__, tty->driver->name); + if (WARN_RATELIMIT(!tty->port, + "%s: %s driver does not set tty->port. This would crash the kernel. Fix the driver!\n", + __func__, tty->driver->name)) { + retval = -EINVAL; + goto err_release_lock; + } + retval = tty_ldisc_lock(tty, 5 * HZ); + if (retval) + goto err_release_lock; tty->port->itty = tty; /* @@ -1479,12 +1431,12 @@ struct tty_struct *tty_init_dev(struct tty_driver *driver, int idx) retval = tty_ldisc_setup(tty, tty->link); if (retval) goto err_release_tty; + tty_ldisc_unlock(tty); /* Return the tty locked so that it cannot vanish under the caller */ return tty; -err_deinit_tty: +err_free_tty: tty_unlock(tty); - deinitialize_tty_struct(tty); free_tty_struct(tty); err_module_put: module_put(driver->owner); @@ -1492,14 +1444,22 @@ err_module_put: /* call the tty release_tty routine to clean out this slot */ err_release_tty: + tty_ldisc_unlock(tty); + tty_info_ratelimited(tty, "ldisc open failed (%d), clearing slot %d\n", + retval, idx); +err_release_lock: tty_unlock(tty); - printk_ratelimited(KERN_INFO "tty_init_dev: ldisc open failed, " - "clearing slot %d\n", idx); release_tty(tty, idx); return ERR_PTR(retval); } -void tty_free_termios(struct tty_struct *tty) +/** + * tty_save_termios() - save tty termios data in driver table + * @tty: tty whose termios data to save + * + * Locking: Caller guarantees serialisation with tty_init_termios(). + */ +void tty_save_termios(struct tty_struct *tty) { struct ktermios *tp; int idx = tty->index; @@ -1511,63 +1471,65 @@ void tty_free_termios(struct tty_struct *tty) /* Stash the termios data */ tp = tty->driver->termios[idx]; if (tp == NULL) { - tp = kmalloc(sizeof(struct ktermios), GFP_KERNEL); - if (tp == NULL) { - pr_warn("tty: no memory to save termios state.\n"); + tp = kmalloc(sizeof(*tp), GFP_KERNEL); + if (tp == NULL) return; - } tty->driver->termios[idx] = tp; } *tp = tty->termios; } -EXPORT_SYMBOL(tty_free_termios); +EXPORT_SYMBOL_GPL(tty_save_termios); /** - * tty_flush_works - flush all works of a tty - * @tty: tty device to flush works for + * tty_flush_works - flush all works of a tty/pty pair + * @tty: tty device to flush works for (or either end of a pty pair) * - * Sync flush all works belonging to @tty. + * Sync flush all works belonging to @tty (and the 'other' tty). */ static void tty_flush_works(struct tty_struct *tty) { flush_work(&tty->SAK_work); flush_work(&tty->hangup_work); + if (tty->link) { + flush_work(&tty->link->SAK_work); + flush_work(&tty->link->hangup_work); + } } /** - * release_one_tty - release tty structure memory - * @kref: kref of tty we are obliterating + * release_one_tty - release tty structure memory + * @work: work of tty we are obliterating * - * Releases memory associated with a tty structure, and clears out the - * driver table slots. This function is called when a device is no longer - * in use. It also gets called when setup of a device fails. + * Releases memory associated with a tty structure, and clears out the + * driver table slots. This function is called when a device is no longer + * in use. It also gets called when setup of a device fails. * - * Locking: - * takes the file list lock internally when working on the list - * of ttys that the driver keeps. + * Locking: + * takes the file list lock internally when working on the list of ttys + * that the driver keeps. * - * This method gets called from a work queue so that the driver private - * cleanup ops can sleep (needed for USB at least) + * This method gets called from a work queue so that the driver private + * cleanup ops can sleep (needed for USB at least) */ static void release_one_tty(struct work_struct *work) { struct tty_struct *tty = container_of(work, struct tty_struct, hangup_work); struct tty_driver *driver = tty->driver; + struct module *owner = driver->owner; if (tty->ops->cleanup) tty->ops->cleanup(tty); - tty->magic = 0; tty_driver_kref_put(driver); - module_put(driver->owner); + module_put(owner); - spin_lock(&tty_files_lock); + spin_lock(&tty->files_lock); list_del_init(&tty->tty_files); - spin_unlock(&tty_files_lock); + spin_unlock(&tty->files_lock); - put_pid(tty->pgrp); - put_pid(tty->session); + put_pid(tty->ctrl.pgrp); + put_pid(tty->ctrl.session); free_tty_struct(tty); } @@ -1576,19 +1538,19 @@ static void queue_release_one_tty(struct kref *kref) struct tty_struct *tty = container_of(kref, struct tty_struct, kref); /* The hangup queue is now free so we can reuse it rather than - waste a chunk of memory for each port */ + * waste a chunk of memory for each port. + */ INIT_WORK(&tty->hangup_work, release_one_tty); schedule_work(&tty->hangup_work); } /** - * tty_kref_put - release a tty kref - * @tty: tty device + * tty_kref_put - release a tty kref + * @tty: tty device * - * Release a reference to a tty device and if need be let the kref - * layer destruct the object for us + * Release a reference to the @tty device and if need be let the kref layer + * destruct the object for us. */ - void tty_kref_put(struct tty_struct *tty) { if (tty) @@ -1597,16 +1559,17 @@ void tty_kref_put(struct tty_struct *tty) EXPORT_SYMBOL(tty_kref_put); /** - * release_tty - release tty structure memory - * - * Release both @tty and a possible linked partner (think pty pair), - * and decrement the refcount of the backing module. + * release_tty - release tty structure memory + * @tty: tty device release + * @idx: index of the tty device release * - * Locking: - * tty_mutex - * takes the file list lock internally when working on the list - * of ttys that the driver keeps. + * Release both @tty and a possible linked partner (think pty pair), + * and decrement the refcount of the backing module. * + * Locking: + * tty_mutex + * takes the file list lock internally when working on the list of ttys + * that the driver keeps. */ static void release_tty(struct tty_struct *tty, int idx) { @@ -1615,34 +1578,34 @@ static void release_tty(struct tty_struct *tty, int idx) WARN_ON(!mutex_is_locked(&tty_mutex)); if (tty->ops->shutdown) tty->ops->shutdown(tty); - tty_free_termios(tty); + tty_save_termios(tty); tty_driver_remove_tty(tty->driver, tty); - tty->port->itty = NULL; + if (tty->port) + tty->port->itty = NULL; if (tty->link) tty->link->port->itty = NULL; - cancel_work_sync(&tty->port->buf.work); - + if (tty->port) + tty_buffer_cancel_work(tty->port); if (tty->link) - tty_kref_put(tty->link); + tty_buffer_cancel_work(tty->link->port); + + tty_kref_put(tty->link); tty_kref_put(tty); } /** - * tty_release_checks - check a tty before real release - * @tty: tty to check - * @o_tty: link of @tty (if any) - * @idx: index of the tty + * tty_release_checks - check a tty before real release + * @tty: tty to check + * @idx: index of the tty * - * Performs some paranoid checking before true release of the @tty. - * This is a no-op unless TTY_PARANOIA_CHECK is defined. + * Performs some paranoid checking before true release of the @tty. This is a + * no-op unless %TTY_PARANOIA_CHECK is defined. */ -static int tty_release_checks(struct tty_struct *tty, struct tty_struct *o_tty, - int idx) +static int tty_release_checks(struct tty_struct *tty, int idx) { #ifdef TTY_PARANOIA_CHECK if (idx < 0 || idx >= tty->driver->num) { - printk(KERN_DEBUG "%s: bad idx when trying to free (%s)\n", - __func__, tty->name); + tty_debug(tty, "bad idx %d\n", idx); return -1; } @@ -1651,18 +1614,20 @@ static int tty_release_checks(struct tty_struct *tty, struct tty_struct *o_tty, return 0; if (tty != tty->driver->ttys[idx]) { - printk(KERN_DEBUG "%s: driver.table[%d] not tty for (%s)\n", - __func__, idx, tty->name); + tty_debug(tty, "bad driver table[%d] = %p\n", + idx, tty->driver->ttys[idx]); return -1; } if (tty->driver->other) { + struct tty_struct *o_tty = tty->link; + if (o_tty != tty->driver->other->ttys[idx]) { - printk(KERN_DEBUG "%s: other->table[%d] not o_tty for (%s)\n", - __func__, idx, tty->name); + tty_debug(tty, "bad other table[%d] = %p\n", + idx, tty->driver->other->ttys[idx]); return -1; } if (o_tty->link != tty) { - printk(KERN_DEBUG "%s: bad pty pointers\n", __func__); + tty_debug(tty, "bad link = %p\n", o_tty->link); return -1; } } @@ -1671,31 +1636,90 @@ static int tty_release_checks(struct tty_struct *tty, struct tty_struct *o_tty, } /** - * tty_release - vfs callback for close - * @inode: inode of tty - * @filp: file pointer for handle to tty + * tty_kclose - closes tty opened by tty_kopen + * @tty: tty device + * + * Performs the final steps to release and free a tty device. It is the same as + * tty_release_struct() except that it also resets %TTY_PORT_KOPENED flag on + * @tty->port. + */ +void tty_kclose(struct tty_struct *tty) +{ + /* + * Ask the line discipline code to release its structures + */ + tty_ldisc_release(tty); + + /* Wait for pending work before tty destruction commences */ + tty_flush_works(tty); + + tty_debug_hangup(tty, "freeing structure\n"); + /* + * The release_tty function takes care of the details of clearing + * the slots and preserving the termios structure. + */ + mutex_lock(&tty_mutex); + tty_port_set_kopened(tty->port, 0); + release_tty(tty, tty->index); + mutex_unlock(&tty_mutex); +} +EXPORT_SYMBOL_GPL(tty_kclose); + +/** + * tty_release_struct - release a tty struct + * @tty: tty device + * @idx: index of the tty + * + * Performs the final steps to release and free a tty device. It is roughly the + * reverse of tty_init_dev(). + */ +void tty_release_struct(struct tty_struct *tty, int idx) +{ + /* + * Ask the line discipline code to release its structures + */ + tty_ldisc_release(tty); + + /* Wait for pending work before tty destruction commmences */ + tty_flush_works(tty); + + tty_debug_hangup(tty, "freeing structure\n"); + /* + * The release_tty function takes care of the details of clearing + * the slots and preserving the termios structure. + */ + mutex_lock(&tty_mutex); + release_tty(tty, idx); + mutex_unlock(&tty_mutex); +} +EXPORT_SYMBOL_GPL(tty_release_struct); + +/** + * tty_release - vfs callback for close + * @inode: inode of tty + * @filp: file pointer for handle to tty * - * Called the last time each file handle is closed that references - * this tty. There may however be several such references. + * Called the last time each file handle is closed that references this tty. + * There may however be several such references. * - * Locking: - * Takes bkl. See tty_release_dev + * Locking: + * Takes BKL. See tty_release_dev(). * - * Even releasing the tty structures is a tricky business.. We have - * to be very careful that the structures are all released at the - * same time, as interrupts might otherwise get the wrong pointers. + * Even releasing the tty structures is a tricky business. We have to be very + * careful that the structures are all released at the same time, as interrupts + * might otherwise get the wrong pointers. * * WSH 09/09/97: rewritten to avoid some nasty race conditions that could * lead to double frees or releasing memory still in use. */ - int tty_release(struct inode *inode, struct file *filp) { struct tty_struct *tty = file_tty(filp); - struct tty_struct *o_tty; - int pty_master, tty_closing, o_tty_closing, do_sleep; + struct tty_struct *o_tty = NULL; + int do_sleep, final; int idx; - char buf[64]; + long timeout = 0; + int once = 1; if (tty_paranoia_check(tty, inode, __func__)) return 0; @@ -1706,25 +1730,23 @@ int tty_release(struct inode *inode, struct file *filp) __tty_fasync(-1, filp, 0); idx = tty->index; - pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY && - tty->driver->subtype == PTY_TYPE_MASTER); - /* Review: parallel close */ - o_tty = tty->link; + if (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER) + o_tty = tty->link; - if (tty_release_checks(tty, o_tty, idx)) { + if (tty_release_checks(tty, idx)) { tty_unlock(tty); return 0; } -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "%s: %s (tty count=%d)...\n", __func__, - tty_name(tty, buf), tty->count); -#endif + tty_debug_hangup(tty, "releasing (count=%d)\n", tty->count); if (tty->ops->close) tty->ops->close(tty, filp); - tty_unlock(tty); + /* If tty is pty master, lock the slave pty (stable lock order) */ + tty_lock_slave(o_tty); + /* * Sanity check: if tty->count is going to zero, there shouldn't be * any waiters on tty->read_wait or tty->write_wait. We test the @@ -1735,72 +1757,54 @@ int tty_release(struct inode *inode, struct file *filp) * The test for the o_tty closing is necessary, since the master and * slave sides may close in any order. If the slave side closes out * first, its count will be one, since the master side holds an open. - * Thus this test wouldn't be triggered at the time the slave closes, + * Thus this test wouldn't be triggered at the time the slave closed, * so we do it now. - * - * Note that it's possible for the tty to be opened again while we're - * flushing out waiters. By recalculating the closing flags before - * each iteration we avoid any problems. */ while (1) { - /* Guard against races with tty->count changes elsewhere and - opens on /dev/tty */ - - mutex_lock(&tty_mutex); - tty_lock_pair(tty, o_tty); - tty_closing = tty->count <= 1; - o_tty_closing = o_tty && - (o_tty->count <= (pty_master ? 1 : 0)); do_sleep = 0; - if (tty_closing) { + if (tty->count <= 1) { if (waitqueue_active(&tty->read_wait)) { - wake_up_poll(&tty->read_wait, POLLIN); + wake_up_poll(&tty->read_wait, EPOLLIN); do_sleep++; } if (waitqueue_active(&tty->write_wait)) { - wake_up_poll(&tty->write_wait, POLLOUT); + wake_up_poll(&tty->write_wait, EPOLLOUT); do_sleep++; } } - if (o_tty_closing) { + if (o_tty && o_tty->count <= 1) { if (waitqueue_active(&o_tty->read_wait)) { - wake_up_poll(&o_tty->read_wait, POLLIN); + wake_up_poll(&o_tty->read_wait, EPOLLIN); do_sleep++; } if (waitqueue_active(&o_tty->write_wait)) { - wake_up_poll(&o_tty->write_wait, POLLOUT); + wake_up_poll(&o_tty->write_wait, EPOLLOUT); do_sleep++; } } if (!do_sleep) break; - printk(KERN_WARNING "%s: %s: read/write wait queue active!\n", - __func__, tty_name(tty, buf)); - tty_unlock_pair(tty, o_tty); - mutex_unlock(&tty_mutex); - schedule(); + if (once) { + once = 0; + tty_warn(tty, "read/write wait queue active!\n"); + } + schedule_timeout_killable(timeout); + if (timeout < 120 * HZ) + timeout = 2 * timeout + 1; + else + timeout = MAX_SCHEDULE_TIMEOUT; } - /* - * The closing flags are now consistent with the open counts on - * both sides, and we've completed the last operation that could - * block, so it's safe to proceed with closing. - * - * We must *not* drop the tty_mutex until we ensure that a further - * entry into tty_open can not pick up this tty. - */ - if (pty_master) { + if (o_tty) { if (--o_tty->count < 0) { - printk(KERN_WARNING "%s: bad pty slave count (%d) for %s\n", - __func__, o_tty->count, tty_name(o_tty, buf)); + tty_warn(tty, "bad slave count (%d)\n", o_tty->count); o_tty->count = 0; } } if (--tty->count < 0) { - printk(KERN_WARNING "%s: bad tty->count (%d) for %s\n", - __func__, tty->count, tty_name(tty, buf)); + tty_warn(tty, "bad tty->count (%d)\n", tty->count); tty->count = 0; } @@ -1818,81 +1822,52 @@ int tty_release(struct inode *inode, struct file *filp) /* * Perform some housekeeping before deciding whether to return. * - * Set the TTY_CLOSING flag if this was the last open. In the - * case of a pty we may have to wait around for the other side - * to close, and TTY_CLOSING makes sure we can't be reopened. - */ - if (tty_closing) - set_bit(TTY_CLOSING, &tty->flags); - if (o_tty_closing) - set_bit(TTY_CLOSING, &o_tty->flags); - - /* * If _either_ side is closing, make sure there aren't any * processes that still think tty or o_tty is their controlling * tty. */ - if (tty_closing || o_tty_closing) { + if (!tty->count) { read_lock(&tasklist_lock); - session_clear_tty(tty->session); + session_clear_tty(tty->ctrl.session); if (o_tty) - session_clear_tty(o_tty->session); + session_clear_tty(o_tty->ctrl.session); read_unlock(&tasklist_lock); } - mutex_unlock(&tty_mutex); - tty_unlock_pair(tty, o_tty); - /* At this point the TTY_CLOSING flag should ensure a dead tty - cannot be re-opened by a racing opener */ - /* check whether both sides are closing ... */ - if (!tty_closing || (o_tty && !o_tty_closing)) - return 0; + final = !tty->count && !(o_tty && o_tty->count); -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "%s: %s: final close\n", __func__, tty_name(tty, buf)); -#endif - /* - * Ask the line discipline code to release its structures + tty_unlock_slave(o_tty); + tty_unlock(tty); + + /* At this point, the tty->count == 0 should ensure a dead tty + * cannot be re-opened by a racing opener. */ - tty_ldisc_release(tty, o_tty); - /* Wait for pending work before tty destruction commmences */ - tty_flush_works(tty); - if (o_tty) - tty_flush_works(o_tty); + if (!final) + return 0; -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "%s: %s: freeing structure...\n", __func__, tty_name(tty, buf)); -#endif - /* - * The release_tty function takes care of the details of clearing - * the slots and preserving the termios structure. The tty_unlock_pair - * should be safe as we keep a kref while the tty is locked (so the - * unlock never unlocks a freed tty). - */ - mutex_lock(&tty_mutex); - release_tty(tty, idx); - mutex_unlock(&tty_mutex); + tty_debug_hangup(tty, "final close\n"); + tty_release_struct(tty, idx); return 0; } /** - * tty_open_current_tty - get tty of current task for open - * @device: device number - * @filp: file pointer to tty - * @return: tty of the current task iff @device is /dev/tty + * tty_open_current_tty - get locked tty of current task + * @device: device number + * @filp: file pointer to tty + * @return: locked tty of the current task iff @device is /dev/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. + * Performs a re-open of the current task's controlling tty. * - * We need to move to returning a refcounted object from all the lookup - * paths including this one. + * 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. */ 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; @@ -1903,51 +1878,58 @@ 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; } /** - * tty_lookup_driver - lookup a tty driver for a given device file - * @device: device number - * @filp: file pointer to tty - * @noctty: set if the device should not become a controlling tty - * @index: index for the device in the @return driver - * @return: driver for this inode (with increased refcount) + * tty_lookup_driver - lookup a tty driver for a given device file + * @device: device number + * @filp: file pointer to tty + * @index: index for the device in the @return driver * - * If @return is not erroneous, the caller is responsible to decrement the - * refcount by tty_driver_kref_put. + * If returned value is not erroneous, the caller is responsible to decrement + * the refcount by tty_driver_kref_put(). * - * Locking: tty_mutex protects get_tty_driver + * Locking: %tty_mutex protects get_tty_driver() + * + * Return: driver for this inode (with increased refcount) */ static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp, - int *noctty, int *index) + int *index) { - struct tty_driver *driver; + struct tty_driver *driver = NULL; switch (device) { #ifdef CONFIG_VT case MKDEV(TTY_MAJOR, 0): { extern struct tty_driver *console_driver; + driver = tty_driver_kref_get(console_driver); *index = fg_console; - *noctty = 1; break; } #endif case MKDEV(TTYAUX_MAJOR, 1): { struct tty_driver *console_driver = console_device(index); + if (console_driver) { driver = tty_driver_kref_get(console_driver); - if (driver) { + if (driver && filp) { /* Don't let /dev/console block */ filp->f_flags |= O_NONBLOCK; - *noctty = 1; break; } } + if (driver) + tty_driver_kref_put(driver); return ERR_PTR(-ENODEV); } default: @@ -1959,113 +1941,207 @@ static struct tty_driver *tty_lookup_driver(dev_t device, struct file *filp, return driver; } +static struct tty_struct *tty_kopen(dev_t device, int shared) +{ + struct tty_struct *tty; + struct tty_driver *driver; + int index = -1; + + mutex_lock(&tty_mutex); + driver = tty_lookup_driver(device, NULL, &index); + if (IS_ERR(driver)) { + mutex_unlock(&tty_mutex); + return ERR_CAST(driver); + } + + /* check whether we're reopening an existing tty */ + tty = tty_driver_lookup_tty(driver, NULL, index); + if (IS_ERR(tty) || shared) + goto out; + + if (tty) { + /* drop kref from tty_driver_lookup_tty() */ + tty_kref_put(tty); + tty = ERR_PTR(-EBUSY); + } else { /* tty_init_dev returns tty with the tty_lock held */ + tty = tty_init_dev(driver, index); + if (IS_ERR(tty)) + goto out; + tty_port_set_kopened(tty->port, 1); + } +out: + mutex_unlock(&tty_mutex); + tty_driver_kref_put(driver); + return tty; +} + /** - * tty_open - open a tty device - * @inode: inode of device file - * @filp: file pointer to tty + * tty_kopen_exclusive - open a tty device for kernel + * @device: dev_t of device to open * - * tty_open and tty_release keep up the tty count that contains the - * number of opens done on a tty. We cannot use the inode-count, as - * different inodes might point to the same tty. + * Opens tty exclusively for kernel. Performs the driver lookup, makes sure + * it's not already opened and performs the first-time tty initialization. * - * Open-counting is needed for pty masters, as well as for keeping - * track of serial lines: DTR is dropped when the last close happens. - * (This is not done solely through tty->count, now. - Ted 1/27/92) + * Claims the global %tty_mutex to serialize: + * * concurrent first-time tty initialization + * * concurrent tty driver removal w/ lookup + * * concurrent tty removal from driver table * - * The termios state of a pty is reset on first open so that - * settings don't persist across reuse. + * Return: the locked initialized &tty_struct + */ +struct tty_struct *tty_kopen_exclusive(dev_t device) +{ + return tty_kopen(device, 0); +} +EXPORT_SYMBOL_GPL(tty_kopen_exclusive); + +/** + * tty_kopen_shared - open a tty device for shared in-kernel use + * @device: dev_t of device to open * - * Locking: tty_mutex protects tty, tty_lookup_driver and tty_init_dev. - * tty->count should protect the rest. - * ->siglock protects ->signal/->sighand + * Opens an already existing tty for in-kernel use. Compared to + * tty_kopen_exclusive() above it doesn't ensure to be the only user. * - * Note: the tty_unlock/lock cases without a ref are only safe due to - * tty_mutex + * Locking: identical to tty_kopen() above. */ +struct tty_struct *tty_kopen_shared(dev_t device) +{ + return tty_kopen(device, 1); +} +EXPORT_SYMBOL_GPL(tty_kopen_shared); -static int tty_open(struct inode *inode, struct file *filp) +/** + * tty_open_by_driver - open a tty device + * @device: dev_t of device to open + * @filp: file pointer to tty + * + * Performs the driver lookup, checks for a reopen, or otherwise performs the + * first-time tty initialization. + * + * + * Claims the global tty_mutex to serialize: + * * concurrent first-time tty initialization + * * concurrent tty driver removal w/ lookup + * * concurrent tty removal from driver table + * + * Return: the locked initialized or re-opened &tty_struct + */ +static struct tty_struct *tty_open_by_driver(dev_t device, + struct file *filp) { struct tty_struct *tty; - int noctty, retval; struct tty_driver *driver = NULL; - int index; - dev_t device = inode->i_rdev; - unsigned saved_flags = filp->f_flags; - - nonseekable_open(inode, filp); - -retry_open: - retval = tty_alloc_file(filp); - if (retval) - return -ENOMEM; - - noctty = filp->f_flags & O_NOCTTY; - index = -1; - retval = 0; + int index = -1; + int retval; 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) { - driver = tty_lookup_driver(device, filp, &noctty, &index); - if (IS_ERR(driver)) { - retval = PTR_ERR(driver); - goto err_unlock; - } + driver = tty_lookup_driver(device, filp, &index); + if (IS_ERR(driver)) { + mutex_unlock(&tty_mutex); + return ERR_CAST(driver); + } - /* check whether we're reopening an existing tty */ - tty = tty_driver_lookup_tty(driver, inode, index); - if (IS_ERR(tty)) { - retval = PTR_ERR(tty); - goto err_unlock; - } + /* check whether we're reopening an existing tty */ + tty = tty_driver_lookup_tty(driver, filp, index); + if (IS_ERR(tty)) { + mutex_unlock(&tty_mutex); + goto out; } if (tty) { - tty_lock(tty); + if (tty_port_kopened(tty->port)) { + tty_kref_put(tty); + mutex_unlock(&tty_mutex); + tty = ERR_PTR(-EBUSY); + goto out; + } + mutex_unlock(&tty_mutex); + retval = tty_lock_interruptible(tty); + tty_kref_put(tty); /* drop kref from tty_driver_lookup_tty() */ + if (retval) { + if (retval == -EINTR) + retval = -ERESTARTSYS; + tty = ERR_PTR(retval); + goto out; + } retval = tty_reopen(tty); if (retval < 0) { tty_unlock(tty); tty = ERR_PTR(retval); } - } else /* Returns with the tty_lock held for now */ + } else { /* Returns with the tty_lock held for now */ tty = tty_init_dev(driver, index); + mutex_unlock(&tty_mutex); + } +out: + tty_driver_kref_put(driver); + return tty; +} + +/** + * tty_open - open a tty device + * @inode: inode of device file + * @filp: file pointer to tty + * + * tty_open() and tty_release() keep up the tty count that contains the number + * of opens done on a tty. We cannot use the inode-count, as different inodes + * might point to the same tty. + * + * Open-counting is needed for pty masters, as well as for keeping track of + * serial lines: DTR is dropped when the last close happens. + * (This is not done solely through tty->count, now. - Ted 1/27/92) + * + * The termios state of a pty is reset on the first open so that settings don't + * persist across reuse. + * + * Locking: + * * %tty_mutex protects tty, tty_lookup_driver() and tty_init_dev(). + * * @tty->count should protect the rest. + * * ->siglock protects ->signal/->sighand + * + * Note: the tty_unlock/lock cases without a ref are only safe due to %tty_mutex + */ +static int tty_open(struct inode *inode, struct file *filp) +{ + struct tty_struct *tty; + int noctty, retval; + dev_t device = inode->i_rdev; + unsigned saved_flags = filp->f_flags; + + nonseekable_open(inode, filp); + +retry_open: + retval = tty_alloc_file(filp); + if (retval) + return -ENOMEM; + + tty = tty_open_current_tty(device, filp); + if (!tty) + tty = tty_open_by_driver(device, filp); - mutex_unlock(&tty_mutex); - if (driver) - tty_driver_kref_put(driver); if (IS_ERR(tty)) { + tty_free_file(filp); retval = PTR_ERR(tty); - goto err_file; + if (retval != -EAGAIN || signal_pending(current)) + return retval; + schedule(); + goto retry_open; } tty_add_file(tty, filp); check_tty_count(tty, __func__); - if (tty->driver->type == TTY_DRIVER_TYPE_PTY && - tty->driver->subtype == PTY_TYPE_MASTER) - noctty = 1; -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "%s: opening %s...\n", __func__, tty->name); -#endif + tty_debug_hangup(tty, "opening (count=%d)\n", tty->count); + if (tty->ops->open) retval = tty->ops->open(tty, filp); else retval = -ENODEV; filp->f_flags = saved_flags; - if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && - !capable(CAP_SYS_ADMIN)) - retval = -EBUSY; - if (retval) { -#ifdef TTY_DEBUG_HANGUP - printk(KERN_DEBUG "%s: error %d in opening %s...\n", __func__, - retval, tty->name); -#endif + tty_debug_hangup(tty, "open error %d, releasing\n", retval); + tty_unlock(tty); /* need to call tty_release without BTM */ tty_release(inode, filp); if (retval != -ERESTARTSYS) @@ -2078,61 +2154,49 @@ retry_open: /* * Need to reset f_op in case a hangup happened. */ - if (filp->f_op == &hung_up_tty_fops) + if (tty_hung_up_p(filp)) filp->f_op = &tty_fops; goto retry_open; } - tty_unlock(tty); + clear_bit(TTY_HUPPED, &tty->flags); - - mutex_lock(&tty_mutex); - tty_lock(tty); - spin_lock_irq(¤t->sighand->siglock); - if (!noctty && - current->signal->leader && - !current->signal->tty && - tty->session == NULL) - __proc_set_tty(current, tty); - spin_unlock_irq(¤t->sighand->siglock); + noctty = (filp->f_flags & O_NOCTTY) || + (IS_ENABLED(CONFIG_VT) && device == MKDEV(TTY_MAJOR, 0)) || + device == MKDEV(TTYAUX_MAJOR, 1) || + (tty->driver->type == TTY_DRIVER_TYPE_PTY && + tty->driver->subtype == PTY_TYPE_MASTER); + if (!noctty) + tty_open_proc_set_tty(filp, tty); tty_unlock(tty); - mutex_unlock(&tty_mutex); return 0; -err_unlock: - mutex_unlock(&tty_mutex); - /* after locks to avoid deadlock */ - if (!IS_ERR_OR_NULL(driver)) - tty_driver_kref_put(driver); -err_file: - tty_free_file(filp); - return retval; } - /** - * tty_poll - check tty status - * @filp: file being polled - * @wait: poll wait structures to update + * tty_poll - check tty status + * @filp: file being polled + * @wait: poll wait structures to update * - * Call the line discipline polling method to obtain the poll - * status of the device. + * Call the line discipline polling method to obtain the poll status of the + * device. * - * Locking: locks called line discipline but ldisc poll method - * may be re-entered freely by other callers. + * Locking: locks called line discipline but ldisc poll method may be + * re-entered freely by other callers. */ - -static unsigned int tty_poll(struct file *filp, poll_table *wait) +static __poll_t tty_poll(struct file *filp, poll_table *wait) { struct tty_struct *tty = file_tty(filp); struct tty_ldisc *ld; - int ret = 0; + __poll_t ret = 0; if (tty_paranoia_check(tty, file_inode(filp), "tty_poll")) return 0; ld = tty_ldisc_ref_wait(tty); + if (!ld) + return hung_up_tty_poll(filp, wait); if (ld->ops->poll) - ret = (ld->ops->poll)(tty, filp, wait); + ret = ld->ops->poll(tty, filp, wait); tty_ldisc_deref(ld); return ret; } @@ -2140,40 +2204,39 @@ static unsigned int tty_poll(struct file *filp, poll_table *wait) static int __tty_fasync(int fd, struct file *filp, int on) { struct tty_struct *tty = file_tty(filp); - struct tty_ldisc *ldisc; unsigned long flags; int retval = 0; if (tty_paranoia_check(tty, file_inode(filp), "tty_fasync")) goto out; + if (on) { + retval = file_f_owner_allocate(filp); + if (retval) + goto out; + } + retval = fasync_helper(fd, filp, on, &tty->fasync); if (retval <= 0) goto out; - ldisc = tty_ldisc_ref(tty); - if (ldisc) { - if (ldisc->ops->fasync) - ldisc->ops->fasync(tty, on); - tty_ldisc_deref(ldisc); - } - if (on) { enum pid_type type; struct pid *pid; - spin_lock_irqsave(&tty->ctrl_lock, flags); - if (tty->pgrp) { - pid = tty->pgrp; + spin_lock_irqsave(&tty->ctrl.lock, flags); + if (tty->ctrl.pgrp) { + pid = tty->ctrl.pgrp; type = PIDTYPE_PGID; } else { pid = task_pid(current); - type = PIDTYPE_PID; + type = PIDTYPE_TGID; } get_pid(pid); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - retval = __f_setown(filp, pid, type, 0); + spin_unlock_irqrestore(&tty->ctrl.lock, flags); + __f_setown(filp, pid, type, 0); put_pid(pid); + retval = 0; } out: return retval; @@ -2182,36 +2245,37 @@ out: static int tty_fasync(int fd, struct file *filp, int on) { struct tty_struct *tty = file_tty(filp); - int retval; + int retval = -ENOTTY; tty_lock(tty); - retval = __tty_fasync(fd, filp, on); + if (!tty_hung_up_p(filp)) + retval = __tty_fasync(fd, filp, on); tty_unlock(tty); return retval; } +static bool tty_legacy_tiocsti __read_mostly = IS_ENABLED(CONFIG_LEGACY_TIOCSTI); /** - * tiocsti - fake input character - * @tty: tty to fake input into - * @p: pointer to character - * - * Fake input to a tty device. Does the necessary locking and - * input management. + * tiocsti - fake input character + * @tty: tty to fake input into + * @p: pointer to character * - * FIXME: does not honour flow control ?? + * Fake input to a tty device. Does the necessary locking and input management. * - * Locking: - * Called functions take tty_ldisc_lock - * current->signal->tty check is safe without locks + * FIXME: does not honour flow control ?? * - * FIXME: may race normal receive processing + * Locking: + * * Called functions take tty_ldiscs_lock + * * current->signal->tty check is safe without locks */ - -static int tiocsti(struct tty_struct *tty, char __user *p) +static int tiocsti(struct tty_struct *tty, u8 __user *p) { - char ch, mbz = 0; struct tty_ldisc *ld; + u8 ch; + + if (!tty_legacy_tiocsti && !capable(CAP_SYS_ADMIN)) + return -EIO; if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN)) return -EPERM; @@ -2219,87 +2283,83 @@ static int tiocsti(struct tty_struct *tty, char __user *p) return -EFAULT; tty_audit_tiocsti(tty, ch); ld = tty_ldisc_ref_wait(tty); - ld->ops->receive_buf(tty, &ch, &mbz, 1); + if (!ld) + return -EIO; + tty_buffer_lock_exclusive(tty->port); + if (ld->ops->receive_buf) + ld->ops->receive_buf(tty, &ch, NULL, 1); + tty_buffer_unlock_exclusive(tty->port); tty_ldisc_deref(ld); return 0; } /** - * tiocgwinsz - implement window query ioctl - * @tty; tty - * @arg: user buffer for result + * tiocgwinsz - implement window query ioctl + * @tty: tty + * @arg: user buffer for result * - * Copies the kernel idea of the window size into the user buffer. + * Copies the kernel idea of the window size into the user buffer. * - * Locking: tty->termios_mutex is taken to ensure the winsize data - * is consistent. + * Locking: @tty->winsize_mutex is taken to ensure the winsize data is + * consistent. */ - static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg) { - int err; + guard(mutex)(&tty->winsize_mutex); - mutex_lock(&tty->termios_mutex); - err = copy_to_user(arg, &tty->winsize, sizeof(*arg)); - mutex_unlock(&tty->termios_mutex); + if (copy_to_user(arg, &tty->winsize, sizeof(*arg))) + return -EFAULT; - return err ? -EFAULT: 0; + return 0; } /** - * tty_do_resize - resize event - * @tty: tty being resized - * @rows: rows (character) - * @cols: cols (character) + * tty_do_resize - resize event + * @tty: tty being resized + * @ws: new dimensions * - * Update the termios variables and send the necessary signals to - * peform a terminal resize correctly + * Update the termios variables and send the necessary signals to peform a + * terminal resize correctly. */ - int tty_do_resize(struct tty_struct *tty, struct winsize *ws) { struct pid *pgrp; - unsigned long flags; - /* Lock the tty */ - mutex_lock(&tty->termios_mutex); + guard(mutex)(&tty->winsize_mutex); + if (!memcmp(ws, &tty->winsize, sizeof(*ws))) - goto done; - /* Get the PID values and reference them so we can - avoid holding the tty ctrl lock while sending signals */ - spin_lock_irqsave(&tty->ctrl_lock, flags); - pgrp = get_pid(tty->pgrp); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + return 0; + /* Signal the foreground process group */ + pgrp = tty_get_pgrp(tty); if (pgrp) kill_pgrp(pgrp, SIGWINCH, 1); put_pid(pgrp); tty->winsize = *ws; -done: - mutex_unlock(&tty->termios_mutex); + return 0; } EXPORT_SYMBOL(tty_do_resize); /** - * tiocswinsz - implement window size set ioctl - * @tty; tty side of tty - * @arg: user buffer for result + * tiocswinsz - implement window size set ioctl + * @tty: tty side of tty + * @arg: user buffer for result * - * Copies the user idea of the window size to the kernel. Traditionally - * this is just advisory information but for the Linux console it - * actually has driver level meaning and triggers a VC resize. + * Copies the user idea of the window size to the kernel. Traditionally this is + * just advisory information but for the Linux console it actually has driver + * level meaning and triggers a VC resize. * - * Locking: - * Driver dependent. The default do_resize method takes the - * tty termios mutex and ctrl_lock. The console takes its own lock - * then calls into the default method. + * Locking: + * Driver dependent. The default do_resize method takes the tty termios + * mutex and ctrl.lock. The console takes its own lock then calls into the + * default method. */ - static int tiocswinsz(struct tty_struct *tty, struct winsize __user *arg) { struct winsize tmp_ws; + if (copy_from_user(&tmp_ws, arg, sizeof(*arg))) return -EFAULT; @@ -2310,20 +2370,20 @@ static int tiocswinsz(struct tty_struct *tty, struct winsize __user *arg) } /** - * tioccons - allow admin to move logical console - * @file: the file to become console + * tioccons - allow admin to move logical console + * @file: the file to become console * - * Allow the administrator to move the redirected console device + * Allow the administrator to move the redirected console device. * - * Locking: uses redirect_lock to guard the redirect information + * Locking: uses redirect_lock to guard the redirect information */ - static int tioccons(struct file *file) { if (!capable(CAP_SYS_ADMIN)) return -EPERM; - if (file->f_op->write == redirected_tty_write) { + if (file->f_op->write_iter == redirected_tty_write) { struct file *f; + spin_lock(&redirect_lock); f = redirect; redirect = NULL; @@ -2332,320 +2392,159 @@ static int tioccons(struct file *file) fput(f); return 0; } - spin_lock(&redirect_lock); - if (redirect) { - spin_unlock(&redirect_lock); - return -EBUSY; - } - redirect = get_file(file); - spin_unlock(&redirect_lock); - return 0; -} + if (file->f_op->write_iter != tty_write) + return -ENOTTY; + if (!(file->f_mode & FMODE_WRITE)) + return -EBADF; + if (!(file->f_mode & FMODE_CAN_WRITE)) + return -EINVAL; -/** - * fionbio - non blocking ioctl - * @file: file to set blocking value - * @p: user parameter - * - * Historical tty interfaces had a blocking control ioctl before - * the generic functionality existed. This piece of history is preserved - * in the expected tty API of posix OS's. - * - * Locking: none, the open file handle ensures it won't go away. - */ + guard(spinlock)(&redirect_lock); -static int fionbio(struct file *file, int __user *p) -{ - int nonblock; + if (redirect) + return -EBUSY; - if (get_user(nonblock, p)) - return -EFAULT; + redirect = get_file(file); - spin_lock(&file->f_lock); - if (nonblock) - file->f_flags |= O_NONBLOCK; - else - file->f_flags &= ~O_NONBLOCK; - spin_unlock(&file->f_lock); return 0; } /** - * tiocsctty - set controlling tty - * @tty: tty structure - * @arg: user argument + * tiocsetd - set line discipline + * @tty: tty device + * @p: pointer to user data * - * This ioctl is used to manage job control. It permits a session - * leader to set this tty as the controlling tty for the session. + * Set the line discipline according to user request. * - * Locking: - * Takes tty_mutex() to protect tty instance - * Takes tasklist_lock internally to walk sessions - * Takes ->siglock() when updating signal->tty + * Locking: see tty_set_ldisc(), this function is just a helper */ - -static int tiocsctty(struct tty_struct *tty, int arg) +static int tiocsetd(struct tty_struct *tty, int __user *p) { - int ret = 0; - if (current->signal->leader && (task_session(current) == tty->session)) - return ret; - - mutex_lock(&tty_mutex); - /* - * The process must be a session leader and - * not have a controlling tty already. - */ - if (!current->signal->leader || current->signal->tty) { - ret = -EPERM; - goto unlock; - } - - if (tty->session) { - /* - * This tty is already the controlling - * tty for another session group! - */ - if (arg == 1 && capable(CAP_SYS_ADMIN)) { - /* - * Steal it away - */ - read_lock(&tasklist_lock); - session_clear_tty(tty->session); - read_unlock(&tasklist_lock); - } else { - ret = -EPERM; - goto unlock; - } - } - proc_set_tty(current, tty); -unlock: - mutex_unlock(&tty_mutex); - return ret; -} - -/** - * tty_get_pgrp - return a ref counted pgrp pid - * @tty: tty to read - * - * Returns a refcounted instance of the pid struct for the process - * group controlling the tty. - */ + int disc; + int ret; -struct pid *tty_get_pgrp(struct tty_struct *tty) -{ - unsigned long flags; - struct pid *pgrp; + if (get_user(disc, p)) + return -EFAULT; - spin_lock_irqsave(&tty->ctrl_lock, flags); - pgrp = get_pid(tty->pgrp); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); + ret = tty_set_ldisc(tty, disc); - return pgrp; + return ret; } -EXPORT_SYMBOL_GPL(tty_get_pgrp); /** - * tiocgpgrp - get process group - * @tty: tty passed by user - * @real_tty: tty side of the tty passed by the user if a pty else the tty - * @p: returned pid + * tiocgetd - get line discipline + * @tty: tty device + * @p: pointer to user data * - * Obtain the process group of the tty. If there is no process group - * return an error. + * Retrieves the line discipline id directly from the ldisc. * - * Locking: none. Reference to current->signal->tty is safe. + * Locking: waits for ldisc reference (in case the line discipline is changing + * or the @tty is being hungup) */ - -static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +static int tiocgetd(struct tty_struct *tty, int __user *p) { - struct pid *pid; + struct tty_ldisc *ld; int ret; - /* - * (tty == real_tty) is a cheap way of - * testing if the tty is NOT a master pty. - */ - if (tty == real_tty && current->signal->tty != real_tty) - return -ENOTTY; - pid = tty_get_pgrp(real_tty); - ret = put_user(pid_vnr(pid), p); - put_pid(pid); + + ld = tty_ldisc_ref_wait(tty); + if (!ld) + return -EIO; + ret = put_user(ld->ops->num, p); + tty_ldisc_deref(ld); return ret; } /** - * tiocspgrp - attempt to set process group - * @tty: tty passed by user - * @real_tty: tty side device matching tty passed by user - * @p: pid pointer + * send_break - performed time break + * @tty: device to break on + * @duration: timeout in mS * - * Set the process group of the tty to the session passed. Only - * permitted where the tty session is our session. + * Perform a timed break on hardware that lacks its own driver level timed + * break functionality. * - * Locking: RCU, ctrl lock + * Locking: + * @tty->atomic_write_lock serializes */ - -static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) +static int send_break(struct tty_struct *tty, unsigned int duration) { - struct pid *pgrp; - pid_t pgrp_nr; - int retval = tty_check_change(real_tty); - unsigned long flags; - - if (retval == -EIO) - return -ENOTTY; - if (retval) - return retval; - if (!current->signal->tty || - (current->signal->tty != real_tty) || - (real_tty->session != task_session(current))) - return -ENOTTY; - if (get_user(pgrp_nr, p)) - return -EFAULT; - if (pgrp_nr < 0) - return -EINVAL; - rcu_read_lock(); - pgrp = find_vpid(pgrp_nr); - retval = -ESRCH; - if (!pgrp) - goto out_unlock; - retval = -EPERM; - if (session_of_pgrp(pgrp) != task_session(current)) - goto out_unlock; - retval = 0; - spin_lock_irqsave(&tty->ctrl_lock, flags); - put_pid(real_tty->pgrp); - real_tty->pgrp = get_pid(pgrp); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); -out_unlock: - rcu_read_unlock(); - return retval; -} - -/** - * tiocgsid - get session id - * @tty: tty passed by user - * @real_tty: tty side of the tty passed by the user if a pty else the tty - * @p: pointer to returned session id - * - * Obtain the session id of the tty. If there is no session - * return an error. - * - * Locking: none. Reference to current->signal->tty is safe. - */ + int retval; -static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) -{ - /* - * (tty == real_tty) is a cheap way of - * testing if the tty is NOT a master pty. - */ - if (tty == real_tty && current->signal->tty != real_tty) - return -ENOTTY; - if (!real_tty->session) - return -ENOTTY; - return put_user(pid_vnr(real_tty->session), p); -} + if (tty->ops->break_ctl == NULL) + return 0; -/** - * tiocsetd - set line discipline - * @tty: tty device - * @p: pointer to user data - * - * Set the line discipline according to user request. - * - * Locking: see tty_set_ldisc, this function is just a helper - */ + if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK) + return tty->ops->break_ctl(tty, duration); -static int tiocsetd(struct tty_struct *tty, int __user *p) -{ - int ldisc; - int ret; + /* Do the work ourselves */ + if (tty_write_lock(tty, false) < 0) + return -EINTR; - if (get_user(ldisc, p)) - return -EFAULT; + retval = tty->ops->break_ctl(tty, -1); + if (!retval) { + msleep_interruptible(duration); + retval = tty->ops->break_ctl(tty, 0); + } else if (retval == -EOPNOTSUPP) { + /* some drivers can tell only dynamically */ + retval = 0; + } + tty_write_unlock(tty); - ret = tty_set_ldisc(tty, ldisc); + if (signal_pending(current)) + retval = -EINTR; - return ret; + return retval; } /** - * send_break - performed time break - * @tty: device to break on - * @duration: timeout in mS - * - * Perform a timed break on hardware that lacks its own driver level - * timed break functionality. - * - * Locking: - * atomic_write_lock serializes + * tty_get_tiocm - get tiocm status register + * @tty: tty device * + * Obtain the modem status bits from the tty driver if the feature + * is supported. */ - -static int send_break(struct tty_struct *tty, unsigned int duration) +int tty_get_tiocm(struct tty_struct *tty) { - int retval; + int retval = -ENOTTY; - if (tty->ops->break_ctl == NULL) - return 0; + if (tty->ops->tiocmget) + retval = tty->ops->tiocmget(tty); - if (tty->driver->flags & TTY_DRIVER_HARDWARE_BREAK) - retval = tty->ops->break_ctl(tty, duration); - else { - /* Do the work ourselves */ - if (tty_write_lock(tty, 0) < 0) - return -EINTR; - retval = tty->ops->break_ctl(tty, -1); - if (retval) - goto out; - if (!signal_pending(current)) - msleep_interruptible(duration); - retval = tty->ops->break_ctl(tty, 0); -out: - tty_write_unlock(tty); - if (signal_pending(current)) - retval = -EINTR; - } return retval; } +EXPORT_SYMBOL_GPL(tty_get_tiocm); /** - * tty_tiocmget - get modem status - * @tty: tty device - * @file: user file pointer - * @p: pointer to result + * tty_tiocmget - get modem status + * @tty: tty device + * @p: pointer to result * - * Obtain the modem status bits from the tty driver if the feature - * is supported. Return -EINVAL if it is not available. + * Obtain the modem status bits from the tty driver if the feature is + * supported. Return -%ENOTTY if it is not available. * - * Locking: none (up to the driver) + * Locking: none (up to the driver) */ - static int tty_tiocmget(struct tty_struct *tty, int __user *p) { - int retval = -EINVAL; + int retval; - if (tty->ops->tiocmget) { - retval = tty->ops->tiocmget(tty); + retval = tty_get_tiocm(tty); + if (retval >= 0) + retval = put_user(retval, p); - if (retval >= 0) - retval = put_user(retval, p); - } return retval; } /** - * tty_tiocmset - set modem status - * @tty: tty device - * @cmd: command - clear bits, set bits or set all - * @p: pointer to desired bits + * tty_tiocmset - set modem status + * @tty: tty device + * @cmd: command - clear bits, set bits or set all + * @p: pointer to desired bits * - * Set the modem status bits from the tty driver if the feature - * is supported. Return -EINVAL if it is not available. + * Set the modem status bits from the tty driver if the feature + * is supported. Return -%ENOTTY if it is not available. * - * Locking: none (up to the driver) + * Locking: none (up to the driver) */ - static int tty_tiocmset(struct tty_struct *tty, unsigned int cmd, unsigned __user *p) { @@ -2653,7 +2552,7 @@ static int tty_tiocmset(struct tty_struct *tty, unsigned int cmd, unsigned int set, clear, val; if (tty->ops->tiocmset == NULL) - return -EINVAL; + return -ENOTTY; retval = get_user(val, p); if (retval) @@ -2676,37 +2575,92 @@ static int tty_tiocmset(struct tty_struct *tty, unsigned int cmd, return tty->ops->tiocmset(tty, set, clear); } +/** + * tty_get_icount - get tty statistics + * @tty: tty device + * @icount: output parameter + * + * Gets a copy of the @tty's icount statistics. + * + * Locking: none (up to the driver) + */ +int tty_get_icount(struct tty_struct *tty, + struct serial_icounter_struct *icount) +{ + memset(icount, 0, sizeof(*icount)); + + if (tty->ops->get_icount) + return tty->ops->get_icount(tty, icount); + else + return -ENOTTY; +} +EXPORT_SYMBOL_GPL(tty_get_icount); + static int tty_tiocgicount(struct tty_struct *tty, void __user *arg) { - int retval = -EINVAL; struct serial_icounter_struct icount; - memset(&icount, 0, sizeof(icount)); - if (tty->ops->get_icount) - retval = tty->ops->get_icount(tty, &icount); + int retval; + + retval = tty_get_icount(tty, &icount); if (retval != 0) return retval; + if (copy_to_user(arg, &icount, sizeof(icount))) return -EFAULT; return 0; } -struct tty_struct *tty_pair_get_tty(struct tty_struct *tty) +static int tty_set_serial(struct tty_struct *tty, struct serial_struct *ss) { - if (tty->driver->type == TTY_DRIVER_TYPE_PTY && - tty->driver->subtype == PTY_TYPE_MASTER) - tty = tty->link; - return tty; + int flags; + + flags = ss->flags & ASYNC_DEPRECATED; + + if (flags) + pr_warn_ratelimited("%s: '%s' is using deprecated serial flags (with no effect): %.8x\n", + __func__, current->comm, flags); + + if (!tty->ops->set_serial) + return -ENOTTY; + + return tty->ops->set_serial(tty, ss); +} + +static int tty_tiocsserial(struct tty_struct *tty, struct serial_struct __user *ss) +{ + struct serial_struct v; + + if (copy_from_user(&v, ss, sizeof(*ss))) + return -EFAULT; + + return tty_set_serial(tty, &v); } -EXPORT_SYMBOL(tty_pair_get_tty); -struct tty_struct *tty_pair_get_pty(struct tty_struct *tty) +static int tty_tiocgserial(struct tty_struct *tty, struct serial_struct __user *ss) +{ + struct serial_struct v; + int err; + + memset(&v, 0, sizeof(v)); + if (!tty->ops->get_serial) + return -ENOTTY; + err = tty->ops->get_serial(tty, &v); + if (!err && copy_to_user(ss, &v, sizeof(v))) + err = -EFAULT; + return err; +} + +/* + * if pty, return the slave side (real_tty) + * otherwise, return self + */ +static struct tty_struct *tty_pair_get_tty(struct tty_struct *tty) { if (tty->driver->type == TTY_DRIVER_TYPE_PTY && tty->driver->subtype == PTY_TYPE_MASTER) - return tty; - return tty->link; + tty = tty->link; + return tty; } -EXPORT_SYMBOL(tty_pair_get_pty); /* * Split this up, as gcc can choke on it otherwise.. @@ -2756,8 +2710,6 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return tiocswinsz(real_tty, p); case TIOCCONS: return real_tty != tty ? -EINVAL : tioccons(file); - case FIONBIO: - return fionbio(file, p); case TIOCEXCL: set_bit(TTY_EXCLUSIVE, &tty->flags); return 0; @@ -2767,23 +2719,11 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case TIOCGEXCL: { int excl = test_bit(TTY_EXCLUSIVE, &tty->flags); + return put_user(excl, (int __user *)p); } - case TIOCNOTTY: - if (current->signal->tty != tty) - return -ENOTTY; - no_tty(); - return 0; - case TIOCSCTTY: - return tiocsctty(tty, arg); - case TIOCGPGRP: - return tiocgpgrp(tty, real_tty, p); - case TIOCSPGRP: - return tiocspgrp(tty, real_tty, p); - case TIOCGSID: - return tiocgsid(tty, real_tty, p); case TIOCGETD: - return put_user(tty->ldisc->ops->num, (int __user *)p); + return tiocgetd(tty, p); case TIOCSETD: return tiocsetd(tty, p); case TIOCVHANGUP: @@ -2794,6 +2734,7 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case TIOCGDEV: { unsigned int ret = new_encode_dev(tty_devnum(real_tty)); + return put_user(ret, (unsigned int __user *)p); } /* @@ -2825,30 +2766,39 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case TIOCMBIS: return tty_tiocmset(tty, cmd, p); case TIOCGICOUNT: - retval = tty_tiocgicount(tty, p); - /* For the moment allow fall through to the old method */ - if (retval != -EINVAL) - return retval; - break; + return tty_tiocgicount(tty, p); case TCFLSH: switch (arg) { case TCIFLUSH: case TCIOFLUSH: /* flush tty buffer and allow ldisc to process ioctl */ - tty_buffer_flush(tty); + tty_buffer_flush(tty, NULL); break; } break; + case TIOCSSERIAL: + return tty_tiocsserial(tty, p); + case TIOCGSERIAL: + return tty_tiocgserial(tty, p); + case TIOCGPTPEER: + /* Special because the struct file is needed */ + return ptm_open_peer(file, tty, (int)arg); + default: + retval = tty_jobctrl_ioctl(tty, real_tty, file, cmd, arg); + if (retval != -ENOIOCTLCMD) + return retval; } if (tty->ops->ioctl) { - retval = (tty->ops->ioctl)(tty, cmd, arg); + retval = tty->ops->ioctl(tty, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } ld = tty_ldisc_ref_wait(tty); + if (!ld) + return hung_up_tty_ioctl(file, cmd, arg); retval = -EINVAL; if (ld->ops->ioctl) { - retval = ld->ops->ioctl(tty, file, cmd, arg); + retval = ld->ops->ioctl(tty, cmd, arg); if (retval == -ENOIOCTLCMD) retval = -ENOTTY; } @@ -2857,6 +2807,71 @@ long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg) } #ifdef CONFIG_COMPAT + +struct serial_struct32 { + compat_int_t type; + compat_int_t line; + compat_uint_t port; + compat_int_t irq; + compat_int_t flags; + compat_int_t xmit_fifo_size; + compat_int_t custom_divisor; + compat_int_t baud_base; + unsigned short close_delay; + char io_type; + char reserved_char; + compat_int_t hub6; + unsigned short closing_wait; /* time to wait before closing */ + unsigned short closing_wait2; /* no longer used... */ + compat_uint_t iomem_base; + unsigned short iomem_reg_shift; + unsigned int port_high; + /* compat_ulong_t iomap_base FIXME */ + compat_int_t reserved; +}; + +static int compat_tty_tiocsserial(struct tty_struct *tty, + struct serial_struct32 __user *ss) +{ + struct serial_struct32 v32; + struct serial_struct v; + + if (copy_from_user(&v32, ss, sizeof(*ss))) + return -EFAULT; + + memcpy(&v, &v32, offsetof(struct serial_struct32, iomem_base)); + v.iomem_base = compat_ptr(v32.iomem_base); + v.iomem_reg_shift = v32.iomem_reg_shift; + v.port_high = v32.port_high; + v.iomap_base = 0; + + return tty_set_serial(tty, &v); +} + +static int compat_tty_tiocgserial(struct tty_struct *tty, + struct serial_struct32 __user *ss) +{ + struct serial_struct32 v32; + struct serial_struct v; + int err; + + memset(&v, 0, sizeof(v)); + memset(&v32, 0, sizeof(v32)); + + if (!tty->ops->get_serial) + return -ENOTTY; + err = tty->ops->get_serial(tty, &v); + if (!err) { + memcpy(&v32, &v, offsetof(struct serial_struct32, iomem_base)); + v32.iomem_base = (unsigned long)v.iomem_base >> 32 ? + 0xfffffff : ptr_to_compat(v.iomem_base); + v32.iomem_reg_shift = v.iomem_reg_shift; + v32.port_high = v.port_high; + if (copy_to_user(ss, &v32, sizeof(v32))) + err = -EFAULT; + } + return err; +} static long tty_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -2864,20 +2879,108 @@ static long tty_compat_ioctl(struct file *file, unsigned int cmd, struct tty_ldisc *ld; int retval = -ENOIOCTLCMD; + switch (cmd) { + case TIOCOUTQ: + case TIOCSTI: + case TIOCGWINSZ: + case TIOCSWINSZ: + case TIOCGEXCL: + case TIOCGETD: + case TIOCSETD: + case TIOCGDEV: + case TIOCMGET: + case TIOCMSET: + case TIOCMBIC: + case TIOCMBIS: + case TIOCGICOUNT: + case TIOCGPGRP: + case TIOCSPGRP: + case TIOCGSID: + case TIOCSERGETLSR: + case TIOCGRS485: + case TIOCSRS485: +#ifdef TIOCGETP + case TIOCGETP: + case TIOCSETP: + case TIOCSETN: +#endif +#ifdef TIOCGETC + case TIOCGETC: + case TIOCSETC: +#endif +#ifdef TIOCGLTC + case TIOCGLTC: + case TIOCSLTC: +#endif + case TCSETSF: + case TCSETSW: + case TCSETS: + case TCGETS: +#ifdef TCGETS2 + case TCGETS2: + case TCSETSF2: + case TCSETSW2: + case TCSETS2: +#endif + case TCGETA: + case TCSETAF: + case TCSETAW: + case TCSETA: + case TIOCGLCKTRMIOS: + case TIOCSLCKTRMIOS: +#ifdef TCGETX + case TCGETX: + case TCSETX: + case TCSETXW: + case TCSETXF: +#endif + case TIOCGSOFTCAR: + case TIOCSSOFTCAR: + + case PPPIOCGCHAN: + case PPPIOCGUNIT: + return tty_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); + case TIOCCONS: + case TIOCEXCL: + case TIOCNXCL: + case TIOCVHANGUP: + case TIOCSBRK: + case TIOCCBRK: + case TCSBRK: + case TCSBRKP: + case TCFLSH: + case TIOCGPTPEER: + case TIOCNOTTY: + case TIOCSCTTY: + case TCXONC: + case TIOCMIWAIT: + case TIOCSERCONFIG: + return tty_ioctl(file, cmd, arg); + } + if (tty_paranoia_check(tty, file_inode(file), "tty_ioctl")) return -EINVAL; + switch (cmd) { + case TIOCSSERIAL: + return compat_tty_tiocsserial(tty, compat_ptr(arg)); + case TIOCGSERIAL: + return compat_tty_tiocgserial(tty, compat_ptr(arg)); + } if (tty->ops->compat_ioctl) { - retval = (tty->ops->compat_ioctl)(tty, cmd, arg); + retval = tty->ops->compat_ioctl(tty, cmd, arg); if (retval != -ENOIOCTLCMD) return retval; } ld = tty_ldisc_ref_wait(tty); + if (!ld) + return hung_up_tty_compat_ioctl(file, cmd, arg); if (ld->ops->compat_ioctl) - retval = ld->ops->compat_ioctl(tty, file, cmd, arg); - else - retval = n_tty_compat_ioctl_helper(tty, file, cmd, arg); + retval = ld->ops->compat_ioctl(tty, cmd, arg); + if (retval == -ENOIOCTLCMD && ld->ops->ioctl) + retval = ld->ops->ioctl(tty, (unsigned long)compat_ptr(cmd), + arg); tty_ldisc_deref(ld); return retval; @@ -2886,11 +2989,11 @@ static long tty_compat_ioctl(struct file *file, unsigned int cmd, static int this_tty(const void *t, struct file *file, unsigned fd) { - if (likely(file->f_op->read != tty_read)) + if (likely(file->f_op->read_iter != tty_read)) return 0; return file_tty(file) != t ? 0 : fd + 1; } - + /* * This implements the "Secure Attention Key" --- the idea is to * prevent trojan horses by killing all processes associated with this @@ -2912,16 +3015,12 @@ static int this_tty(const void *t, struct file *file, unsigned fd) */ void __do_SAK(struct tty_struct *tty) { -#ifdef TTY_SOFT_SAK - tty_hangup(tty); -#else struct task_struct *g, *p; struct pid *session; - int i; + int i; - if (!tty) - return; - session = tty->session; + scoped_guard(spinlock_irqsave, &tty->ctrl.lock) + session = get_pid(tty->ctrl.session); tty_ldisc_flush(tty); @@ -2930,34 +3029,31 @@ void __do_SAK(struct tty_struct *tty) read_lock(&tasklist_lock); /* Kill the entire session */ do_each_pid_task(session, PIDTYPE_SID, p) { - printk(KERN_NOTICE "SAK: killed process %d" - " (%s): task_session(p)==tty->session\n", - task_pid_nr(p), p->comm); - send_sig(SIGKILL, p, 1); + tty_notice(tty, "SAK: killed process %d (%s): by session\n", + task_pid_nr(p), p->comm); + group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, PIDTYPE_SID); } while_each_pid_task(session, PIDTYPE_SID, p); - /* Now kill any processes that happen to have the - * tty open. - */ - do_each_thread(g, p) { + + /* Now kill any processes that happen to have the tty open */ + for_each_process_thread(g, p) { if (p->signal->tty == tty) { - printk(KERN_NOTICE "SAK: killed process %d" - " (%s): task_session(p)==tty->session\n", - task_pid_nr(p), p->comm); - send_sig(SIGKILL, p, 1); + tty_notice(tty, "SAK: killed process %d (%s): by controlling tty\n", + task_pid_nr(p), p->comm); + group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, + PIDTYPE_SID); continue; } - task_lock(p); + guard(task_lock)(p); i = iterate_fd(p->files, 0, this_tty, tty); if (i != 0) { - printk(KERN_NOTICE "SAK: killed process %d" - " (%s): fd#%d opened to the tty\n", - task_pid_nr(p), p->comm, i - 1); - force_sig(SIGKILL, p); + tty_notice(tty, "SAK: killed process %d (%s): by fd#%d\n", + task_pid_nr(p), p->comm, i - 1); + group_send_sig_info(SIGKILL, SEND_SIG_PRIV, p, + PIDTYPE_SID); } - task_unlock(p); - } while_each_thread(g, p); + } read_unlock(&tasklist_lock); -#endif + put_pid(session); } static void do_SAK_work(struct work_struct *work) @@ -2979,50 +3075,53 @@ void do_SAK(struct tty_struct *tty) return; schedule_work(&tty->SAK_work); } - EXPORT_SYMBOL(do_SAK); -static int dev_match_devt(struct device *dev, const void *data) -{ - const dev_t *devt = data; - return dev->devt == *devt; -} - /* Must put_device() after it's unused! */ static struct device *tty_get_device(struct tty_struct *tty) { dev_t devt = tty_devnum(tty); - return class_find_device(tty_class, NULL, &devt, dev_match_devt); + + return class_find_device_by_devt(&tty_class, devt); } /** - * initialize_tty_struct - * @tty: tty to initialize + * alloc_tty_struct - allocate a new tty + * @driver: driver which will handle the returned tty + * @idx: minor of the tty * - * This subroutine initializes a tty structure that has been newly - * allocated. + * This subroutine allocates and initializes a tty structure. * - * Locking: none - tty in question must not be exposed at this point + * Locking: none - @tty in question is not exposed at this point */ - -void initialize_tty_struct(struct tty_struct *tty, - struct tty_driver *driver, int idx) +struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx) { - memset(tty, 0, sizeof(struct tty_struct)); + struct tty_struct *tty; + + tty = kzalloc(sizeof(*tty), GFP_KERNEL_ACCOUNT); + if (!tty) + return NULL; + kref_init(&tty->kref); - tty->magic = TTY_MAGIC; - tty_ldisc_init(tty); - tty->session = NULL; - tty->pgrp = NULL; + if (tty_ldisc_init(tty)) { + kfree(tty); + return NULL; + } + tty->ctrl.session = NULL; + tty->ctrl.pgrp = NULL; mutex_init(&tty->legacy_mutex); - mutex_init(&tty->termios_mutex); - mutex_init(&tty->ldisc_mutex); + mutex_init(&tty->throttle_mutex); + init_rwsem(&tty->termios_rwsem); + mutex_init(&tty->winsize_mutex); + init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); INIT_WORK(&tty->hangup_work, do_tty_hangup); mutex_init(&tty->atomic_write_lock); - spin_lock_init(&tty->ctrl_lock); + spin_lock_init(&tty->ctrl.lock); + spin_lock_init(&tty->flow.lock); + spin_lock_init(&tty->files_lock); INIT_LIST_HEAD(&tty->tty_files); INIT_WORK(&tty->SAK_work, do_SAK_work); @@ -3031,35 +3130,24 @@ void initialize_tty_struct(struct tty_struct *tty, tty->index = idx; tty_line_name(driver, idx, tty->name); tty->dev = tty_get_device(tty); -} -/** - * deinitialize_tty_struct - * @tty: tty to deinitialize - * - * This subroutine deinitializes a tty structure that has been newly - * allocated but tty_release cannot be called on that yet. - * - * Locking: none - tty in question must not be exposed at this point - */ -void deinitialize_tty_struct(struct tty_struct *tty) -{ - tty_ldisc_deinit(tty); + return tty; } /** - * tty_put_char - write one character to a tty - * @tty: tty - * @ch: character + * tty_put_char - write one character to a tty + * @tty: tty + * @ch: character to write + * + * Write one byte to the @tty using the provided @tty->ops->put_char() method + * if present. * - * Write one byte to the tty using the provided put_char method - * if present. Returns the number of characters successfully output. + * Note: the specific put_char operation in the driver layer may go + * away soon. Don't call it directly, use this method * - * Note: the specific put_char operation in the driver layer may go - * away soon. Don't call it directly, use this method + * Return: the number of characters successfully output. */ - -int tty_put_char(struct tty_struct *tty, unsigned char ch) +int tty_put_char(struct tty_struct *tty, u8 ch) { if (tty->ops->put_char) return tty->ops->put_char(tty, ch); @@ -3067,36 +3155,41 @@ int tty_put_char(struct tty_struct *tty, unsigned char ch) } EXPORT_SYMBOL_GPL(tty_put_char); -struct class *tty_class; - static int tty_cdev_add(struct tty_driver *driver, dev_t dev, unsigned int index, unsigned int count) { + int err; + /* init here, since reused cdevs cause crashes */ - cdev_init(&driver->cdevs[index], &tty_fops); - driver->cdevs[index].owner = driver->owner; - return cdev_add(&driver->cdevs[index], dev, count); + driver->cdevs[index] = cdev_alloc(); + if (!driver->cdevs[index]) + return -ENOMEM; + driver->cdevs[index]->ops = &tty_fops; + driver->cdevs[index]->owner = driver->owner; + err = cdev_add(driver->cdevs[index], dev, count); + if (err) + kobject_put(&driver->cdevs[index]->kobj); + return err; } /** - * tty_register_device - register a tty device - * @driver: the tty driver that describes the tty device - * @index: the index in the tty driver for this tty device - * @device: a struct device that is associated with this tty device. - * This field is optional, if there is no known struct device - * for this tty device it can be set to NULL safely. + * tty_register_device - register a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * @device: a struct device that is associated with this tty device. + * This field is optional, if there is no known struct device + * for this tty device it can be set to NULL safely. * - * Returns a pointer to the struct device for this tty device - * (or ERR_PTR(-EFOO) on error). + * This call is required to be made to register an individual tty device + * if the tty driver's flags have the %TTY_DRIVER_DYNAMIC_DEV bit set. If + * that bit is not set, this function should not be called by a tty + * driver. * - * This call is required to be made to register an individual tty device - * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If - * that bit is not set, this function should not be called by a tty - * driver. + * Locking: ?? * - * Locking: ?? + * Return: A pointer to the struct device for this tty device (or + * ERR_PTR(-EFOO) on error). */ - struct device *tty_register_device(struct tty_driver *driver, unsigned index, struct device *device) { @@ -3106,29 +3199,28 @@ EXPORT_SYMBOL(tty_register_device); static void tty_device_create_release(struct device *dev) { - pr_debug("device: '%s': %s\n", dev_name(dev), __func__); + dev_dbg(dev, "releasing...\n"); kfree(dev); } /** - * tty_register_device_attr - register a tty device - * @driver: the tty driver that describes the tty device - * @index: the index in the tty driver for this tty device - * @device: a struct device that is associated with this tty device. - * This field is optional, if there is no known struct device - * for this tty device it can be set to NULL safely. - * @drvdata: Driver data to be set to device. - * @attr_grp: Attribute group to be set on device. + * tty_register_device_attr - register a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device + * @device: a struct device that is associated with this tty device. + * This field is optional, if there is no known struct device + * for this tty device it can be set to %NULL safely. + * @drvdata: Driver data to be set to device. + * @attr_grp: Attribute group to be set on device. * - * Returns a pointer to the struct device for this tty device - * (or ERR_PTR(-EFOO) on error). + * This call is required to be made to register an individual tty device if the + * tty driver's flags have the %TTY_DRIVER_DYNAMIC_DEV bit set. If that bit is + * not set, this function should not be called by a tty driver. * - * This call is required to be made to register an individual tty device - * if the tty driver's flags have the TTY_DRIVER_DYNAMIC_DEV bit set. If - * that bit is not set, this function should not be called by a tty - * driver. + * Locking: ?? * - * Locking: ?? + * Return: A pointer to the struct device for this tty device (or + * ERR_PTR(-EFOO) on error). */ struct device *tty_register_device_attr(struct tty_driver *driver, unsigned index, struct device *device, @@ -3137,13 +3229,13 @@ struct device *tty_register_device_attr(struct tty_driver *driver, { char name[64]; dev_t devt = MKDEV(driver->major, driver->minor_start) + index; - struct device *dev = NULL; - int retval = -ENODEV; - bool cdev = false; + struct ktermios *tp; + struct device *dev; + int retval; if (index >= driver->num) { - printk(KERN_ERR "Attempt to register invalid tty line number " - " (%d).\n", index); + pr_err("%s: Attempt to register invalid tty line number (%d)\n", + driver->name, index); return ERR_PTR(-EINVAL); } @@ -3152,69 +3244,84 @@ struct device *tty_register_device_attr(struct tty_driver *driver, else tty_line_name(driver, index, name); - if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { - retval = tty_cdev_add(driver, devt, index, 1); - if (retval) - goto error; - cdev = true; - } - dev = kzalloc(sizeof(*dev), GFP_KERNEL); - if (!dev) { - retval = -ENOMEM; - goto error; - } + if (!dev) + return ERR_PTR(-ENOMEM); dev->devt = devt; - dev->class = tty_class; + dev->class = &tty_class; dev->parent = device; dev->release = tty_device_create_release; dev_set_name(dev, "%s", name); dev->groups = attr_grp; dev_set_drvdata(dev, drvdata); + dev_set_uevent_suppress(dev, 1); + retval = device_register(dev); if (retval) - goto error; + goto err_put; + + if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { + /* + * Free any saved termios data so that the termios state is + * reset when reusing a minor number. + */ + tp = driver->termios[index]; + if (tp) { + driver->termios[index] = NULL; + kfree(tp); + } + + retval = tty_cdev_add(driver, devt, index, 1); + if (retval) + goto err_del; + } + + dev_set_uevent_suppress(dev, 0); + kobject_uevent(&dev->kobj, KOBJ_ADD); return dev; -error: +err_del: + device_del(dev); +err_put: put_device(dev); - if (cdev) - cdev_del(&driver->cdevs[index]); + return ERR_PTR(retval); } EXPORT_SYMBOL_GPL(tty_register_device_attr); /** - * tty_unregister_device - unregister a tty device - * @driver: the tty driver that describes the tty device - * @index: the index in the tty driver for this tty device + * tty_unregister_device - unregister a tty device + * @driver: the tty driver that describes the tty device + * @index: the index in the tty driver for this tty device * - * If a tty device is registered with a call to tty_register_device() then - * this function must be called when the tty device is gone. + * If a tty device is registered with a call to tty_register_device() then + * this function must be called when the tty device is gone. * - * Locking: ?? + * Locking: ?? */ - void tty_unregister_device(struct tty_driver *driver, unsigned index) { - device_destroy(tty_class, - MKDEV(driver->major, driver->minor_start) + index); - if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) - cdev_del(&driver->cdevs[index]); + device_destroy(&tty_class, MKDEV(driver->major, driver->minor_start) + index); + if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { + cdev_del(driver->cdevs[index]); + driver->cdevs[index] = NULL; + } } EXPORT_SYMBOL(tty_unregister_device); /** - * __tty_alloc_driver -- allocate tty driver + * __tty_alloc_driver - allocate tty driver * @lines: count of lines this driver can handle at most - * @owner: module which is repsonsible for this driver - * @flags: some of TTY_DRIVER_* flags, will be set in driver->flags + * @owner: module which is responsible for this driver + * @flags: some of enum tty_driver_flag, will be set in driver->flags * - * This should not be called directly, some of the provided macros should be - * used instead. Use IS_ERR and friends on @retval. + * This should not be called directly, tty_alloc_driver() should be used + * instead. + * + * Returns: struct tty_driver or a PTR-encoded error (use IS_ERR() and friends). */ struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner, unsigned long flags) @@ -3226,12 +3333,11 @@ struct tty_driver *__tty_alloc_driver(unsigned int lines, struct module *owner, if (!lines || (flags & TTY_DRIVER_UNNUMBERED_NODE && lines > 1)) return ERR_PTR(-EINVAL); - driver = kzalloc(sizeof(struct tty_driver), GFP_KERNEL); + driver = kzalloc(sizeof(*driver), GFP_KERNEL); if (!driver) return ERR_PTR(-ENOMEM); kref_init(&driver->kref); - driver->magic = TTY_DRIVER_MAGIC; driver->num = lines; driver->owner = owner; driver->flags = flags; @@ -3268,6 +3374,7 @@ err_free_all: kfree(driver->ports); kfree(driver->ttys); kfree(driver->termios); + kfree(driver->cdevs); kfree(driver); return ERR_PTR(err); } @@ -3280,11 +3387,6 @@ static void destruct_tty_driver(struct kref *kref) struct ktermios *tp; if (driver->flags & TTY_DRIVER_INSTALLED) { - /* - * Free the termios and termios_locked structures because - * we don't want to get memory leaks when modular tty - * drivers are removed from the kernel. - */ for (i = 0; i < driver->num; i++) { tp = driver->termios[i]; if (tp) { @@ -3296,7 +3398,7 @@ static void destruct_tty_driver(struct kref *kref) } proc_tty_unregister_driver(driver); if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) - cdev_del(&driver->cdevs[0]); + cdev_del(driver->cdevs[0]); } kfree(driver->cdevs); kfree(driver->ports); @@ -3305,26 +3407,22 @@ static void destruct_tty_driver(struct kref *kref) kfree(driver); } +/** + * tty_driver_kref_put - drop a reference to a tty driver + * @driver: driver of which to drop the reference + * + * The final put will destroy and free up the driver. + */ void tty_driver_kref_put(struct tty_driver *driver) { kref_put(&driver->kref, destruct_tty_driver); } EXPORT_SYMBOL(tty_driver_kref_put); -void tty_set_operations(struct tty_driver *driver, - const struct tty_operations *op) -{ - driver->ops = op; -}; -EXPORT_SYMBOL(tty_set_operations); - -void put_tty_driver(struct tty_driver *d) -{ - tty_driver_kref_put(d); -} -EXPORT_SYMBOL(put_tty_driver); - -/* +/** + * tty_register_driver - register a tty driver + * @driver: driver to register + * * Called by a tty driver to register itself. */ int tty_register_driver(struct tty_driver *driver) @@ -3354,9 +3452,8 @@ int tty_register_driver(struct tty_driver *driver) goto err_unreg_char; } - mutex_lock(&tty_mutex); - list_add(&driver->tty_drivers, &tty_drivers); - mutex_unlock(&tty_mutex); + scoped_guard(mutex, &tty_mutex) + list_add(&driver->tty_drivers, &tty_drivers); if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { for (i = 0; i < driver->num; i++) { @@ -3375,9 +3472,8 @@ err_unreg_devs: for (i--; i >= 0; i--) tty_unregister_device(driver, i); - mutex_lock(&tty_mutex); - list_del(&driver->tty_drivers); - mutex_unlock(&tty_mutex); + scoped_guard(mutex, &tty_mutex) + list_del(&driver->tty_drivers); err_unreg_char: unregister_chrdev_region(dev, driver->num); @@ -3386,24 +3482,19 @@ err: } EXPORT_SYMBOL(tty_register_driver); -/* +/** + * tty_unregister_driver - unregister a tty driver + * @driver: driver to unregister + * * Called by a tty driver to unregister itself. */ -int tty_unregister_driver(struct tty_driver *driver) +void tty_unregister_driver(struct tty_driver *driver) { -#if 0 - /* FIXME */ - if (driver->refcount) - return -EBUSY; -#endif unregister_chrdev_region(MKDEV(driver->major, driver->minor_start), driver->num); - mutex_lock(&tty_mutex); - list_del(&driver->tty_drivers); - mutex_unlock(&tty_mutex); - return 0; + scoped_guard(mutex, &tty_mutex) + list_del(&driver->tty_drivers); } - EXPORT_SYMBOL(tty_unregister_driver); dev_t tty_devnum(struct tty_struct *tty) @@ -3412,89 +3503,12 @@ dev_t tty_devnum(struct tty_struct *tty) } EXPORT_SYMBOL(tty_devnum); -void proc_clear_tty(struct task_struct *p) -{ - unsigned long flags; - struct tty_struct *tty; - spin_lock_irqsave(&p->sighand->siglock, flags); - tty = p->signal->tty; - p->signal->tty = NULL; - spin_unlock_irqrestore(&p->sighand->siglock, flags); - tty_kref_put(tty); -} - -/* Called under the sighand lock */ - -static void __proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) -{ - if (tty) { - unsigned long flags; - /* We should not have a session or pgrp to put here but.... */ - spin_lock_irqsave(&tty->ctrl_lock, flags); - put_pid(tty->session); - put_pid(tty->pgrp); - tty->pgrp = get_pid(task_pgrp(tsk)); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); - tty->session = get_pid(task_session(tsk)); - if (tsk->signal->tty) { - printk(KERN_DEBUG "tty not NULL!!\n"); - tty_kref_put(tsk->signal->tty); - } - } - put_pid(tsk->signal->tty_old_pgrp); - tsk->signal->tty = tty_kref_get(tty); - tsk->signal->tty_old_pgrp = NULL; -} - -static void proc_set_tty(struct task_struct *tsk, struct tty_struct *tty) -{ - spin_lock_irq(&tsk->sighand->siglock); - __proc_set_tty(tsk, tty); - spin_unlock_irq(&tsk->sighand->siglock); -} - -struct tty_struct *get_current_tty(void) -{ - struct tty_struct *tty; - unsigned long flags; - - spin_lock_irqsave(¤t->sighand->siglock, flags); - tty = tty_kref_get(current->signal->tty); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - return tty; -} -EXPORT_SYMBOL_GPL(get_current_tty); - void tty_default_fops(struct file_operations *fops) { *fops = tty_fops; } -/* - * Initialize the console device. This is called *early*, so - * we can't necessarily depend on lots of kernel help here. - * Just do some early initializations, and do the complex setup - * later. - */ -void __init console_init(void) -{ - initcall_t *call; - - /* Setup the default TTY line discipline. */ - tty_ldisc_begin(); - - /* - * set up the console device so that later boot sequences can - * inform about problems etc.. - */ - call = __con_initcall_start; - while (call < __con_initcall_end) { - (*call)(); - call++; - } -} - -static char *tty_devnode(struct device *dev, umode_t *mode) +static char *tty_devnode(const struct device *dev, umode_t *mode) { if (!mode) return NULL; @@ -3504,13 +3518,14 @@ static char *tty_devnode(struct device *dev, umode_t *mode) return NULL; } +const struct class tty_class = { + .name = "tty", + .devnode = tty_devnode, +}; + static int __init tty_class_init(void) { - tty_class = class_create(THIS_MODULE, "tty"); - if (IS_ERR(tty_class)) - return PTR_ERR(tty_class); - tty_class->devnode = tty_devnode; - return 0; + return class_register(&tty_class); } postcore_initcall(tty_class_init); @@ -3526,11 +3541,18 @@ static ssize_t show_cons_active(struct device *dev, struct console *c; ssize_t count = 0; - console_lock(); + /* + * Hold the console_list_lock to guarantee that no consoles are + * unregistered until all console processing is complete. + * This also allows safe traversal of the console list and + * race-free reading of @flags. + */ + console_list_lock(); + for_each_console(c) { if (!c->device) continue; - if (!c->write) + if (!(c->flags & CON_NBCON) && !c->write) continue; if ((c->flags & CON_ENABLED) == 0) continue; @@ -3538,15 +3560,41 @@ static ssize_t show_cons_active(struct device *dev, if (i >= ARRAY_SIZE(cs)) break; } - while (i--) - count += sprintf(buf + count, "%s%d%c", - cs[i]->name, cs[i]->index, i ? ' ':'\n'); + + /* + * Take console_lock to serialize device() callback with + * other console operations. For example, fg_console is + * modified under console_lock when switching vt. + */ + console_lock(); + while (i--) { + int index = cs[i]->index; + struct tty_driver *drv = cs[i]->device(cs[i], &index); + + /* don't resolve tty0 as some programs depend on it */ + if (drv && (cs[i]->index > 0 || drv->major != TTY_MAJOR)) + count += tty_line_name(drv, index, buf + count); + else + count += sprintf(buf + count, "%s%d", + cs[i]->name, cs[i]->index); + + count += sprintf(buf + count, "%c", i ? ' ':'\n'); + } console_unlock(); + console_list_unlock(); + return count; } static DEVICE_ATTR(active, S_IRUGO, show_cons_active, NULL); +static struct attribute *cons_dev_attrs[] = { + &dev_attr_active.attr, + NULL +}; + +ATTRIBUTE_GROUPS(cons_dev); + static struct device *consdev; void console_sysfs_notify(void) @@ -3555,32 +3603,50 @@ void console_sysfs_notify(void) sysfs_notify(&consdev->kobj, NULL, "active"); } +static const struct ctl_table tty_table[] = { + { + .procname = "legacy_tiocsti", + .data = &tty_legacy_tiocsti, + .maxlen = sizeof(tty_legacy_tiocsti), + .mode = 0644, + .proc_handler = proc_dobool, + }, + { + .procname = "ldisc_autoload", + .data = &tty_ldisc_autoload, + .maxlen = sizeof(tty_ldisc_autoload), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = SYSCTL_ONE, + }, +}; + /* * Ok, now we can initialize the rest of the tty devices and can count * on memory allocations, interrupts etc.. */ int __init tty_init(void) { + register_sysctl_init("dev/tty", tty_table); cdev_init(&tty_cdev, &tty_fops); if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) || register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0) panic("Couldn't register /dev/tty driver\n"); - device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty"); + device_create(&tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty"); cdev_init(&console_cdev, &console_fops); if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) || register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0) panic("Couldn't register /dev/console driver\n"); - consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL, - "console"); + consdev = device_create_with_groups(&tty_class, NULL, + MKDEV(TTYAUX_MAJOR, 1), NULL, + cons_dev_groups, "console"); if (IS_ERR(consdev)) consdev = NULL; - else - WARN_ON(device_create_file(consdev, &dev_attr_active) < 0); #ifdef CONFIG_VT vty_init(&console_fops); #endif return 0; } - |
