2005-04-17 05:20:36 +07:00
|
|
|
/*
|
|
|
|
* n_tty.c --- implements the N_TTY line discipline.
|
2008-02-08 19:18:44 +07:00
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* This code used to be in tty_io.c, but things are getting hairy
|
|
|
|
* enough that it made sense to split things off. (The N_TTY
|
|
|
|
* processing has changed so much that it's hardly recognizable,
|
|
|
|
* anyway...)
|
|
|
|
*
|
|
|
|
* Note that the open routine for N_TTY is guaranteed never to return
|
|
|
|
* an error. This is because Linux will fall back to setting a line
|
2008-02-08 19:18:44 +07:00
|
|
|
* to N_TTY if it can not switch to any other line discipline.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* Written by Theodore Ts'o, Copyright 1994.
|
2008-02-08 19:18:44 +07:00
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* This file also contains code originally written by Linus Torvalds,
|
|
|
|
* Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994.
|
2008-02-08 19:18:44 +07:00
|
|
|
*
|
2005-04-17 05:20:36 +07:00
|
|
|
* This file may be redistributed under the terms of the GNU General Public
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* Reduced memory usage for older ARM systems - Russell King.
|
|
|
|
*
|
2008-02-08 19:18:44 +07:00
|
|
|
* 2000/01/20 Fixed SMP locking on put_tty_queue using bits of
|
2005-04-17 05:20:36 +07:00
|
|
|
* the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
|
|
|
|
* who actually finally proved there really was a race.
|
|
|
|
*
|
|
|
|
* 2002/03/18 Implemented n_tty_wakeup to send SIGIO POLL_OUTs to
|
|
|
|
* waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
|
2008-10-13 16:46:24 +07:00
|
|
|
* Also fixed a bug in BLOCKING mode where n_tty_write returns
|
2005-04-17 05:20:36 +07:00
|
|
|
* EAGAIN
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/major.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/signal.h>
|
|
|
|
#include <linux/fcntl.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/timer.h>
|
|
|
|
#include <linux/ctype.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/poll.h>
|
|
|
|
#include <linux/bitops.h>
|
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is
required by various security standards such as DCID 6/3 and PCI to provide
non-repudiation of administrator's actions and to allow a review of past
actions if the administrator seems to overstep their duties or if the system
becomes misconfigured for unknown reasons. These requirements do not make it
necessary to audit TTY output as well.
Compared to an user-space keylogger, this approach records TTY input using the
audit subsystem, correlated with other audit events, and it is completely
transparent to the user-space application (e.g. the console ioctls still
work).
TTY input auditing works on a higher level than auditing all system calls
within the session, which would produce an overwhelming amount of mostly
useless audit events.
Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs
by process with the attribute is sent to the audit subsystem by the kernel.
The audit netlink interface is extended to allow modifying the audit_tty
attribute, and to allow sending explanatory audit events from user-space (for
example, a shell might send an event containing the final command, after the
interactive command-line editing and history expansion is performed, which
might be difficult to decipher from the TTY input alone).
Because the "audit_tty" attribute is inherited across fork (), it would be set
e.g. for sshd restarted within an audited session. To prevent this, the
audit_tty attribute is cleared when a process with no open TTY file
descriptors (e.g. after daemon startup) opens a TTY.
See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a
more detailed rationale document for an older version of this patch.
[akpm@linux-foundation.org: build fix]
Signed-off-by: Miloslav Trmac <mitr@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Fulghum <paulkf@microgate.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Steve Grubb <sgrubb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-07-16 13:40:56 +07:00
|
|
|
#include <linux/audit.h>
|
|
|
|
#include <linux/file.h>
|
2009-01-02 20:41:04 +07:00
|
|
|
#include <linux/uaccess.h>
|
2010-03-11 06:23:46 +07:00
|
|
|
#include <linux/module.h>
|
2013-02-12 14:00:43 +07:00
|
|
|
#include <linux/ratelimit.h>
|
2013-07-24 20:30:05 +07:00
|
|
|
#include <linux/vmalloc.h>
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
|
|
|
|
/* number of characters left in xmit buffer before select has we have room */
|
|
|
|
#define WAKEUP_CHARS 256
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This defines the low- and high-watermarks for throttling and
|
|
|
|
* unthrottling the TTY driver. These watermarks are used for
|
|
|
|
* controlling the space in the read buffer.
|
|
|
|
*/
|
|
|
|
#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
|
2011-12-08 23:47:33 +07:00
|
|
|
#define TTY_THRESHOLD_UNTHROTTLE 128
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
/*
|
|
|
|
* Special byte codes used in the echo buffer to represent operations
|
|
|
|
* or special handling of characters. Bytes in the echo buffer that
|
|
|
|
* are not part of such special blocks are treated as normal character
|
|
|
|
* codes.
|
|
|
|
*/
|
|
|
|
#define ECHO_OP_START 0xff
|
|
|
|
#define ECHO_OP_MOVE_BACK_COL 0x80
|
|
|
|
#define ECHO_OP_SET_CANON_COL 0x81
|
|
|
|
#define ECHO_OP_ERASE_TAB 0x82
|
|
|
|
|
2013-06-15 21:04:26 +07:00
|
|
|
#define ECHO_COMMIT_WATERMARK 256
|
|
|
|
#define ECHO_BLOCK 256
|
|
|
|
#define ECHO_DISCARD_WATERMARK N_TTY_BUF_SIZE - (ECHO_BLOCK + 32)
|
|
|
|
|
|
|
|
|
2013-06-15 20:14:17 +07:00
|
|
|
#undef N_TTY_TRACE
|
|
|
|
#ifdef N_TTY_TRACE
|
|
|
|
# define n_tty_trace(f, args...) trace_printk(f, ##args)
|
|
|
|
#else
|
|
|
|
# define n_tty_trace(f, args...)
|
|
|
|
#endif
|
|
|
|
|
2012-10-19 03:26:38 +07:00
|
|
|
struct n_tty_data {
|
2013-06-15 20:14:30 +07:00
|
|
|
/* producer-published */
|
|
|
|
size_t read_head;
|
2015-01-17 03:05:37 +07:00
|
|
|
size_t commit_head;
|
2013-06-15 20:14:30 +07:00
|
|
|
size_t canon_head;
|
2013-06-15 21:04:29 +07:00
|
|
|
size_t echo_head;
|
|
|
|
size_t echo_commit;
|
2013-12-10 06:06:07 +07:00
|
|
|
size_t echo_mark;
|
2013-06-15 21:21:20 +07:00
|
|
|
DECLARE_BITMAP(char_map, 256);
|
2013-06-15 20:14:30 +07:00
|
|
|
|
|
|
|
/* private to n_tty_receive_overrun (single-threaded) */
|
2012-10-19 03:26:39 +07:00
|
|
|
unsigned long overrun_time;
|
|
|
|
int num_overrun;
|
|
|
|
|
2013-06-15 20:14:15 +07:00
|
|
|
/* non-atomic */
|
|
|
|
bool no_room;
|
|
|
|
|
2013-06-15 20:14:30 +07:00
|
|
|
/* must hold exclusive termios_rwsem to reset these */
|
2012-10-19 03:26:39 +07:00
|
|
|
unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1;
|
2013-12-11 05:12:02 +07:00
|
|
|
unsigned char push:1;
|
2012-10-19 03:26:40 +07:00
|
|
|
|
2013-06-15 20:14:30 +07:00
|
|
|
/* shared by producer and consumer */
|
2013-06-15 21:21:19 +07:00
|
|
|
char read_buf[N_TTY_BUF_SIZE];
|
2012-10-19 03:26:40 +07:00
|
|
|
DECLARE_BITMAP(read_flags, N_TTY_BUF_SIZE);
|
2013-06-15 21:21:19 +07:00
|
|
|
unsigned char echo_buf[N_TTY_BUF_SIZE];
|
2012-10-19 03:26:41 +07:00
|
|
|
|
2013-06-15 18:28:28 +07:00
|
|
|
int minimum_to_wake;
|
2012-10-19 03:26:41 +07:00
|
|
|
|
2013-06-15 20:14:30 +07:00
|
|
|
/* consumer-published */
|
|
|
|
size_t read_tail;
|
2013-06-15 21:21:17 +07:00
|
|
|
size_t line_start;
|
2013-06-15 20:14:30 +07:00
|
|
|
|
|
|
|
/* protected by output lock */
|
|
|
|
unsigned int column;
|
2012-10-19 03:26:41 +07:00
|
|
|
unsigned int canon_column;
|
2013-06-15 21:04:29 +07:00
|
|
|
size_t echo_tail;
|
2012-10-19 03:26:42 +07:00
|
|
|
|
|
|
|
struct mutex atomic_read_lock;
|
|
|
|
struct mutex output_lock;
|
2012-10-19 03:26:38 +07:00
|
|
|
};
|
|
|
|
|
2013-06-15 20:14:20 +07:00
|
|
|
static inline size_t read_cnt(struct n_tty_data *ldata)
|
|
|
|
{
|
2013-06-15 20:14:22 +07:00
|
|
|
return ldata->read_head - ldata->read_tail;
|
2013-06-15 20:14:20 +07:00
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:21 +07:00
|
|
|
static inline unsigned char read_buf(struct n_tty_data *ldata, size_t i)
|
|
|
|
{
|
|
|
|
return ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned char *read_buf_addr(struct n_tty_data *ldata, size_t i)
|
|
|
|
{
|
|
|
|
return &ldata->read_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:04:22 +07:00
|
|
|
static inline unsigned char echo_buf(struct n_tty_data *ldata, size_t i)
|
|
|
|
{
|
|
|
|
return ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned char *echo_buf_addr(struct n_tty_data *ldata, size_t i)
|
|
|
|
{
|
|
|
|
return &ldata->echo_buf[i & (N_TTY_BUF_SIZE - 1)];
|
|
|
|
}
|
|
|
|
|
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is
required by various security standards such as DCID 6/3 and PCI to provide
non-repudiation of administrator's actions and to allow a review of past
actions if the administrator seems to overstep their duties or if the system
becomes misconfigured for unknown reasons. These requirements do not make it
necessary to audit TTY output as well.
Compared to an user-space keylogger, this approach records TTY input using the
audit subsystem, correlated with other audit events, and it is completely
transparent to the user-space application (e.g. the console ioctls still
work).
TTY input auditing works on a higher level than auditing all system calls
within the session, which would produce an overwhelming amount of mostly
useless audit events.
Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs
by process with the attribute is sent to the audit subsystem by the kernel.
The audit netlink interface is extended to allow modifying the audit_tty
attribute, and to allow sending explanatory audit events from user-space (for
example, a shell might send an event containing the final command, after the
interactive command-line editing and history expansion is performed, which
might be difficult to decipher from the TTY input alone).
Because the "audit_tty" attribute is inherited across fork (), it would be set
e.g. for sshd restarted within an audited session. To prevent this, the
audit_tty attribute is cleared when a process with no open TTY file
descriptors (e.g. after daemon startup) opens a TTY.
See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a
more detailed rationale document for an older version of this patch.
[akpm@linux-foundation.org: build fix]
Signed-off-by: Miloslav Trmac <mitr@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Fulghum <paulkf@microgate.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Steve Grubb <sgrubb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-07-16 13:40:56 +07:00
|
|
|
static inline int tty_put_user(struct tty_struct *tty, unsigned char x,
|
|
|
|
unsigned char __user *ptr)
|
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
|
|
|
tty_audit_add_data(tty, &x, 1, ldata->icanon);
|
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is
required by various security standards such as DCID 6/3 and PCI to provide
non-repudiation of administrator's actions and to allow a review of past
actions if the administrator seems to overstep their duties or if the system
becomes misconfigured for unknown reasons. These requirements do not make it
necessary to audit TTY output as well.
Compared to an user-space keylogger, this approach records TTY input using the
audit subsystem, correlated with other audit events, and it is completely
transparent to the user-space application (e.g. the console ioctls still
work).
TTY input auditing works on a higher level than auditing all system calls
within the session, which would produce an overwhelming amount of mostly
useless audit events.
Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs
by process with the attribute is sent to the audit subsystem by the kernel.
The audit netlink interface is extended to allow modifying the audit_tty
attribute, and to allow sending explanatory audit events from user-space (for
example, a shell might send an event containing the final command, after the
interactive command-line editing and history expansion is performed, which
might be difficult to decipher from the TTY input alone).
Because the "audit_tty" attribute is inherited across fork (), it would be set
e.g. for sshd restarted within an audited session. To prevent this, the
audit_tty attribute is cleared when a process with no open TTY file
descriptors (e.g. after daemon startup) opens a TTY.
See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a
more detailed rationale document for an older version of this patch.
[akpm@linux-foundation.org: build fix]
Signed-off-by: Miloslav Trmac <mitr@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Fulghum <paulkf@microgate.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Steve Grubb <sgrubb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-07-16 13:40:56 +07:00
|
|
|
return put_user(x, ptr);
|
|
|
|
}
|
|
|
|
|
2015-11-28 02:11:02 +07:00
|
|
|
static int tty_copy_to_user(struct tty_struct *tty, void __user *to,
|
|
|
|
size_t tail, size_t n)
|
2015-05-15 01:42:17 +07:00
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2015-11-28 02:11:02 +07:00
|
|
|
size_t size = N_TTY_BUF_SIZE - tail;
|
|
|
|
const void *from = read_buf_addr(ldata, tail);
|
|
|
|
int uncopied;
|
|
|
|
|
|
|
|
if (n > size) {
|
|
|
|
tty_audit_add_data(tty, from, size, ldata->icanon);
|
|
|
|
uncopied = copy_to_user(to, from, size);
|
|
|
|
if (uncopied)
|
|
|
|
return uncopied;
|
|
|
|
to += size;
|
|
|
|
n -= size;
|
|
|
|
from = ldata->read_buf;
|
|
|
|
}
|
2015-05-15 01:42:17 +07:00
|
|
|
|
2015-11-08 20:52:31 +07:00
|
|
|
tty_audit_add_data(tty, from, n, ldata->icanon);
|
2015-05-15 01:42:17 +07:00
|
|
|
return copy_to_user(to, from, n);
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:15 +07:00
|
|
|
/**
|
2015-01-17 03:05:34 +07:00
|
|
|
* n_tty_kick_worker - start input worker (if required)
|
2013-06-15 20:14:15 +07:00
|
|
|
* @tty: terminal
|
|
|
|
*
|
2015-01-17 03:05:34 +07:00
|
|
|
* Re-schedules the flip buffer work if it may have stopped
|
2013-06-15 20:14:15 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* Caller holds exclusive termios_rwsem
|
|
|
|
* or
|
|
|
|
* n_tty_read()/consumer path:
|
|
|
|
* holds non-exclusive termios_rwsem
|
2013-06-15 20:14:15 +07:00
|
|
|
*/
|
|
|
|
|
2015-01-17 03:05:34 +07:00
|
|
|
static void n_tty_kick_worker(struct tty_struct *tty)
|
2013-06-15 18:28:31 +07:00
|
|
|
{
|
2013-06-15 20:14:15 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2015-01-17 03:05:34 +07:00
|
|
|
/* Did the input worker stop? Restart it */
|
|
|
|
if (unlikely(ldata->no_room)) {
|
2013-06-15 20:14:15 +07:00
|
|
|
ldata->no_room = 0;
|
|
|
|
|
2012-10-19 03:26:47 +07:00
|
|
|
WARN_RATELIMIT(tty->port->itty == NULL,
|
2012-10-26 01:26:35 +07:00
|
|
|
"scheduling with invalid itty\n");
|
2013-03-12 03:44:21 +07:00
|
|
|
/* see if ldisc has been killed - if so, this means that
|
|
|
|
* even though the ldisc has been halted and ->buf.work
|
|
|
|
* cancelled, ->buf.work is about to be rescheduled
|
|
|
|
*/
|
|
|
|
WARN_RATELIMIT(test_bit(TTY_LDISC_HALTED, &tty->flags),
|
|
|
|
"scheduling buffer work for halted ldisc\n");
|
2015-10-18 03:36:23 +07:00
|
|
|
tty_buffer_restart_work(tty->port);
|
2012-10-19 03:26:47 +07:00
|
|
|
}
|
2011-06-04 04:33:24 +07:00
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:32 +07:00
|
|
|
static ssize_t chars_in_buffer(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
ssize_t n = 0;
|
|
|
|
|
|
|
|
if (!ldata->icanon)
|
2015-01-17 03:05:37 +07:00
|
|
|
n = ldata->commit_head - ldata->read_tail;
|
2013-06-15 20:14:32 +07:00
|
|
|
else
|
|
|
|
n = ldata->canon_head - ldata->read_tail;
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:34 +07:00
|
|
|
/**
|
|
|
|
* n_tty_write_wakeup - asynchronous I/O notifier
|
|
|
|
* @tty: tty device
|
|
|
|
*
|
|
|
|
* Required for the ptys, serial driver etc. since processes
|
|
|
|
* that attach themselves to the master and rely on ASYNC
|
|
|
|
* IO must be woken up
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void n_tty_write_wakeup(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
if (tty->fasync && test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags))
|
|
|
|
kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:22 +07:00
|
|
|
static void n_tty_check_throttle(struct tty_struct *tty)
|
2013-06-15 20:14:33 +07:00
|
|
|
{
|
2015-01-17 03:05:35 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2013-06-15 20:14:33 +07:00
|
|
|
/*
|
|
|
|
* Check the remaining room for the input canonicalization
|
|
|
|
* mode. We don't want to throttle the driver if we're in
|
|
|
|
* canonical mode and don't have a newline yet!
|
|
|
|
*/
|
2015-01-17 03:05:35 +07:00
|
|
|
if (ldata->icanon && ldata->canon_head == ldata->read_tail)
|
|
|
|
return;
|
|
|
|
|
2013-06-15 20:14:33 +07:00
|
|
|
while (1) {
|
|
|
|
int throttled;
|
|
|
|
tty_set_flow_change(tty, TTY_THROTTLE_SAFE);
|
2015-01-17 03:05:36 +07:00
|
|
|
if (N_TTY_BUF_SIZE - read_cnt(ldata) >= TTY_THRESHOLD_THROTTLE)
|
2013-06-15 20:14:33 +07:00
|
|
|
break;
|
|
|
|
throttled = tty_throttle_safe(tty);
|
|
|
|
if (!throttled)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
__tty_set_flow_change(tty, 0);
|
|
|
|
}
|
|
|
|
|
2013-07-24 19:29:55 +07:00
|
|
|
static void n_tty_check_unthrottle(struct tty_struct *tty)
|
2013-06-15 20:14:33 +07:00
|
|
|
{
|
2013-07-23 19:47:30 +07:00
|
|
|
if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
|
|
|
|
tty->link->ldisc->ops->write_wakeup == n_tty_write_wakeup) {
|
|
|
|
if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
|
|
|
|
return;
|
|
|
|
if (!tty->count)
|
|
|
|
return;
|
2015-01-17 03:05:34 +07:00
|
|
|
n_tty_kick_worker(tty);
|
2013-07-23 19:47:30 +07:00
|
|
|
n_tty_write_wakeup(tty->link);
|
2013-12-03 02:24:46 +07:00
|
|
|
if (waitqueue_active(&tty->link->write_wait))
|
|
|
|
wake_up_interruptible_poll(&tty->link->write_wait, POLLOUT);
|
2013-07-23 19:47:30 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:33 +07:00
|
|
|
/* If there is enough space in the read buffer now, let the
|
|
|
|
* low-level driver know. We use chars_in_buffer() to
|
|
|
|
* check the buffer, as it now knows about canonical mode.
|
|
|
|
* Otherwise, if the driver is throttled and the line is
|
|
|
|
* longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
|
|
|
|
* we won't get any more characters.
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
int unthrottled;
|
|
|
|
tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
|
|
|
|
if (chars_in_buffer(tty) > TTY_THRESHOLD_UNTHROTTLE)
|
|
|
|
break;
|
|
|
|
if (!tty->count)
|
|
|
|
break;
|
2015-01-17 03:05:34 +07:00
|
|
|
n_tty_kick_worker(tty);
|
2013-06-15 20:14:33 +07:00
|
|
|
unthrottled = tty_unthrottle_safe(tty);
|
|
|
|
if (!unthrottled)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
__tty_set_flow_change(tty, 0);
|
|
|
|
}
|
|
|
|
|
2008-10-13 16:45:06 +07:00
|
|
|
/**
|
|
|
|
* put_tty_queue - add character to tty
|
|
|
|
* @c: character
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* Add a character to the tty read_buf queue.
|
|
|
|
*
|
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
2008-10-13 16:45:06 +07:00
|
|
|
*/
|
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
static inline void put_tty_queue(unsigned char c, struct n_tty_data *ldata)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2014-11-13 11:53:26 +07:00
|
|
|
*read_buf_addr(ldata, ldata->read_head) = c;
|
|
|
|
ldata->read_head++;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* reset_buffer_flags - reset buffer state
|
|
|
|
* @tty: terminal to reset
|
|
|
|
*
|
2013-03-12 03:44:31 +07:00
|
|
|
* Reset the read buffer counters and clear the flags.
|
|
|
|
* Called from n_tty_open() and n_tty_flush_buffer().
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* Locking: caller holds exclusive termios_rwsem
|
|
|
|
* (or locking is not required)
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2013-03-12 03:44:32 +07:00
|
|
|
static void reset_buffer_flags(struct n_tty_data *ldata)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-06-15 20:14:25 +07:00
|
|
|
ldata->read_head = ldata->canon_head = ldata->read_tail = 0;
|
2013-06-15 21:04:24 +07:00
|
|
|
ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0;
|
2015-01-17 03:05:37 +07:00
|
|
|
ldata->commit_head = 0;
|
2013-12-10 06:06:07 +07:00
|
|
|
ldata->echo_mark = 0;
|
2013-06-15 21:21:17 +07:00
|
|
|
ldata->line_start = 0;
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2013-06-15 20:14:25 +07:00
|
|
|
ldata->erasing = 0;
|
2012-10-19 03:26:40 +07:00
|
|
|
bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
|
2013-12-11 05:12:02 +07:00
|
|
|
ldata->push = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-03-12 03:44:22 +07:00
|
|
|
static void n_tty_packet_mode_flush(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
if (tty->link->packet) {
|
2014-10-17 02:33:26 +07:00
|
|
|
spin_lock_irqsave(&tty->ctrl_lock, flags);
|
2013-03-12 03:44:22 +07:00
|
|
|
tty->ctrl_status |= TIOCPKT_FLUSHREAD;
|
2014-10-17 02:33:26 +07:00
|
|
|
spin_unlock_irqrestore(&tty->ctrl_lock, flags);
|
tty: fix stall caused by missing memory barrier in drivers/tty/n_tty.c
My colleague ran into a program stall on a x86_64 server, where
n_tty_read() was waiting for data even if there was data in the buffer
in the pty. kernel stack for the stuck process looks like below.
#0 [ffff88303d107b58] __schedule at ffffffff815c4b20
#1 [ffff88303d107bd0] schedule at ffffffff815c513e
#2 [ffff88303d107bf0] schedule_timeout at ffffffff815c7818
#3 [ffff88303d107ca0] wait_woken at ffffffff81096bd2
#4 [ffff88303d107ce0] n_tty_read at ffffffff8136fa23
#5 [ffff88303d107dd0] tty_read at ffffffff81368013
#6 [ffff88303d107e20] __vfs_read at ffffffff811a3704
#7 [ffff88303d107ec0] vfs_read at ffffffff811a3a57
#8 [ffff88303d107f00] sys_read at ffffffff811a4306
#9 [ffff88303d107f50] entry_SYSCALL_64_fastpath at ffffffff815c86d7
There seems to be two problems causing this issue.
First, in drivers/tty/n_tty.c, __receive_buf() stores the data and
updates ldata->commit_head using smp_store_release() and then checks
the wait queue using waitqueue_active(). However, since there is no
memory barrier, __receive_buf() could return without calling
wake_up_interactive_poll(), and at the same time, n_tty_read() could
start to wait in wait_woken() as in the following chart.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
if (waitqueue_active(&tty->read_wait))
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
add_wait_queue(&tty->read_wait, &wait);
...
if (!input_available_p(tty, 0)) {
smp_store_release(&ldata->commit_head,
ldata->read_head);
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
The second problem is that n_tty_read() also lacks a memory barrier
call and could also cause __receive_buf() to return without calling
wake_up_interactive_poll(), and n_tty_read() to wait in wait_woken()
as in the chart below.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
spin_lock_irqsave(&q->lock, flags);
/* from add_wait_queue() */
...
if (!input_available_p(tty, 0)) {
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
smp_store_release(&ldata->commit_head,
ldata->read_head);
if (waitqueue_active(&tty->read_wait))
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock,flags);
/* from add_wait_queue() */
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
There are also other places in drivers/tty/n_tty.c which have similar
calls to waitqueue_active(), so instead of adding many memory barrier
calls, this patch simply removes the call to waitqueue_active(),
leaving just wake_up*() behind.
This fixes both problems because, even though the memory access before
or after the spinlocks in both wake_up*() and add_wait_queue() can
sneak into the critical section, it cannot go past it and the critical
section assures that they will be serialized (please see "INTER-CPU
ACQUIRING BARRIER EFFECTS" in Documentation/memory-barriers.txt for a
better explanation). Moreover, the resulting code is much simpler.
Latency measurement using a ping-pong test over a pty doesn't show any
visible performance drop.
Signed-off-by: Kosuke Tatsukawa <tatsu@ab.jp.nec.com>
Cc: stable@vger.kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-10-02 15:27:05 +07:00
|
|
|
wake_up_interruptible(&tty->link->read_wait);
|
2013-03-12 03:44:22 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* n_tty_flush_buffer - clean input queue
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
2013-03-12 03:44:31 +07:00
|
|
|
* Flush the input buffer. Called when the tty layer wants the
|
|
|
|
* buffer flushed (eg at hangup) or when the N_TTY line discipline
|
|
|
|
* internally has to clean the pending queue (for example some signals).
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* Holds termios_rwsem to exclude producer/consumer while
|
|
|
|
* buffer indices are reset.
|
|
|
|
*
|
|
|
|
* Locking: ctrl_lock, exclusive termios_rwsem
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
|
|
|
static void n_tty_flush_buffer(struct tty_struct *tty)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-06-15 20:14:26 +07:00
|
|
|
down_write(&tty->termios_rwsem);
|
2013-03-12 03:44:32 +07:00
|
|
|
reset_buffer_flags(tty->disc_data);
|
2015-01-17 03:05:34 +07:00
|
|
|
n_tty_kick_worker(tty);
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2013-03-12 03:44:22 +07:00
|
|
|
if (tty->link)
|
|
|
|
n_tty_packet_mode_flush(tty);
|
2013-06-15 20:14:26 +07:00
|
|
|
up_write(&tty->termios_rwsem);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:26 +07:00
|
|
|
/**
|
|
|
|
* n_tty_chars_in_buffer - report available bytes
|
|
|
|
* @tty: tty device
|
|
|
|
*
|
|
|
|
* Report the number of characters buffered to be delivered to user
|
|
|
|
* at this instant in time.
|
|
|
|
*
|
|
|
|
* Locking: exclusive termios_rwsem
|
|
|
|
*/
|
|
|
|
|
2013-06-15 20:14:18 +07:00
|
|
|
static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
|
|
|
|
{
|
2013-06-15 20:14:26 +07:00
|
|
|
ssize_t n;
|
|
|
|
|
2013-06-15 20:14:19 +07:00
|
|
|
WARN_ONCE(1, "%s is deprecated and scheduled for removal.", __func__);
|
2013-06-15 20:14:26 +07:00
|
|
|
|
|
|
|
down_write(&tty->termios_rwsem);
|
|
|
|
n = chars_in_buffer(tty);
|
|
|
|
up_write(&tty->termios_rwsem);
|
|
|
|
return n;
|
2013-06-15 20:14:18 +07:00
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* is_utf8_continuation - utf8 multibyte check
|
|
|
|
* @c: byte to check
|
|
|
|
*
|
|
|
|
* Returns true if the utf8 character 'c' is a multibyte continuation
|
|
|
|
* character. We use this to correctly compute the on screen size
|
|
|
|
* of the character when printing
|
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static inline int is_utf8_continuation(unsigned char c)
|
|
|
|
{
|
|
|
|
return (c & 0xc0) == 0x80;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* is_continuation - multibyte check
|
|
|
|
* @c: byte to check
|
|
|
|
*
|
|
|
|
* Returns true if the utf8 character 'c' is a multibyte continuation
|
|
|
|
* character and the terminal is in unicode mode.
|
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static inline int is_continuation(unsigned char c, struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
return I_IUTF8(tty) && is_utf8_continuation(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-01-02 20:40:53 +07:00
|
|
|
* do_output_char - output one character
|
2005-04-17 05:20:36 +07:00
|
|
|
* @c: character (or partial unicode symbol)
|
|
|
|
* @tty: terminal device
|
2009-01-02 20:40:53 +07:00
|
|
|
* @space: space available in tty driver write buffer
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2009-01-02 20:40:53 +07:00
|
|
|
* This is a helper function that handles one output character
|
|
|
|
* (including special characters like TAB, CR, LF, etc.),
|
2009-09-10 04:03:13 +07:00
|
|
|
* doing OPOST processing and putting the results in the
|
|
|
|
* tty driver's write buffer.
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Note that Linux currently ignores TABDLY, CRDLY, VTDLY, FFDLY
|
|
|
|
* and NLDLY. They simply aren't relevant in the world today.
|
|
|
|
* If you ever need them, add them here.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2009-01-02 20:40:53 +07:00
|
|
|
* Returns the number of bytes of buffer space used or -1 if
|
|
|
|
* no space left.
|
|
|
|
*
|
|
|
|
* Locking: should be called under the output_lock to protect
|
|
|
|
* the column state and space left in the buffer
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
static int do_output_char(unsigned char c, struct tty_struct *tty, int space)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2009-01-02 20:40:53 +07:00
|
|
|
int spaces;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (!space)
|
|
|
|
return -1;
|
2009-01-02 20:41:04 +07:00
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
switch (c) {
|
|
|
|
case '\n':
|
|
|
|
if (O_ONLRET(tty))
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column = 0;
|
2009-01-02 20:40:53 +07:00
|
|
|
if (O_ONLCR(tty)) {
|
|
|
|
if (space < 2)
|
|
|
|
return -1;
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column = 0;
|
2009-09-06 02:46:07 +07:00
|
|
|
tty->ops->write(tty, "\r\n", 2);
|
2009-01-02 20:40:53 +07:00
|
|
|
return 2;
|
|
|
|
}
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
case '\r':
|
2012-10-19 03:26:39 +07:00
|
|
|
if (O_ONOCR(tty) && ldata->column == 0)
|
2009-01-02 20:40:53 +07:00
|
|
|
return 0;
|
|
|
|
if (O_OCRNL(tty)) {
|
|
|
|
c = '\n';
|
|
|
|
if (O_ONLRET(tty))
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column = 0;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
case '\t':
|
2012-10-19 03:26:39 +07:00
|
|
|
spaces = 8 - (ldata->column & 7);
|
2009-01-02 20:40:53 +07:00
|
|
|
if (O_TABDLY(tty) == XTABS) {
|
|
|
|
if (space < spaces)
|
|
|
|
return -1;
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column += spaces;
|
2009-01-02 20:40:53 +07:00
|
|
|
tty->ops->write(tty, " ", spaces);
|
|
|
|
return spaces;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column += spaces;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
case '\b':
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->column > 0)
|
|
|
|
ldata->column--;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
default:
|
2009-01-02 20:43:25 +07:00
|
|
|
if (!iscntrl(c)) {
|
|
|
|
if (O_OLCUC(tty))
|
|
|
|
c = toupper(c);
|
|
|
|
if (!is_continuation(c, tty))
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column++;
|
2009-01-02 20:43:25 +07:00
|
|
|
}
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2008-04-30 14:54:13 +07:00
|
|
|
tty_put_char(tty, c);
|
2009-01-02 20:40:53 +07:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* process_output - output post processor
|
|
|
|
* @c: character (or partial unicode symbol)
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
2009-09-10 04:03:13 +07:00
|
|
|
* Output one character with OPOST processing.
|
|
|
|
* Returns -1 when the output device is full and the character
|
|
|
|
* must be retried.
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Locking: output_lock to protect column state and space left
|
|
|
|
* (also, this is called from n_tty_write under the
|
|
|
|
* tty layer write lock)
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int process_output(unsigned char c, struct tty_struct *tty)
|
|
|
|
{
|
2012-10-19 03:26:42 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2009-01-02 20:40:53 +07:00
|
|
|
int space, retval;
|
|
|
|
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_lock(&ldata->output_lock);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
|
|
|
space = tty_write_room(tty);
|
|
|
|
retval = do_output_char(c, tty, space);
|
|
|
|
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
2009-01-02 20:40:53 +07:00
|
|
|
if (retval < 0)
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-01-02 20:40:53 +07:00
|
|
|
* process_output_block - block post processor
|
2005-04-17 05:20:36 +07:00
|
|
|
* @tty: terminal device
|
2009-09-10 04:03:13 +07:00
|
|
|
* @buf: character buffer
|
|
|
|
* @nr: number of bytes to output
|
|
|
|
*
|
|
|
|
* Output a block of characters with OPOST processing.
|
|
|
|
* Returns the number of characters output.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* This path is used to speed up block console writes, among other
|
|
|
|
* things when processing blocks of output data. It handles only
|
|
|
|
* the simple cases normally found and helps to generate blocks of
|
|
|
|
* symbols for the console driver and thus improve performance.
|
|
|
|
*
|
2009-01-02 20:40:53 +07:00
|
|
|
* Locking: output_lock to protect column state and space left
|
|
|
|
* (also, this is called from n_tty_write under the
|
|
|
|
* tty layer write lock)
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
static ssize_t process_output_block(struct tty_struct *tty,
|
|
|
|
const unsigned char *buf, unsigned int nr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
int space;
|
2011-12-08 23:47:33 +07:00
|
|
|
int i;
|
2005-04-17 05:20:36 +07:00
|
|
|
const unsigned char *cp;
|
|
|
|
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_lock(&ldata->output_lock);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2008-04-30 14:54:13 +07:00
|
|
|
space = tty_write_room(tty);
|
2009-01-02 20:41:04 +07:00
|
|
|
if (!space) {
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
if (nr > space)
|
|
|
|
nr = space;
|
|
|
|
|
|
|
|
for (i = 0, cp = buf; i < nr; i++, cp++) {
|
2009-01-02 20:43:25 +07:00
|
|
|
unsigned char c = *cp;
|
|
|
|
|
|
|
|
switch (c) {
|
2005-04-17 05:20:36 +07:00
|
|
|
case '\n':
|
|
|
|
if (O_ONLRET(tty))
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (O_ONLCR(tty))
|
|
|
|
goto break_out;
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
case '\r':
|
2012-10-19 03:26:39 +07:00
|
|
|
if (O_ONOCR(tty) && ldata->column == 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
goto break_out;
|
|
|
|
if (O_OCRNL(tty))
|
|
|
|
goto break_out;
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
case '\t':
|
|
|
|
goto break_out;
|
|
|
|
case '\b':
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->column > 0)
|
|
|
|
ldata->column--;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
default:
|
2009-01-02 20:43:25 +07:00
|
|
|
if (!iscntrl(c)) {
|
|
|
|
if (O_OLCUC(tty))
|
|
|
|
goto break_out;
|
|
|
|
if (!is_continuation(c, tty))
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column++;
|
2009-01-02 20:43:25 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break_out:
|
2008-04-30 14:54:13 +07:00
|
|
|
i = tty->ops->write(tty, buf, i);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
/**
|
|
|
|
* process_echoes - write pending echo characters
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
|
|
|
* Write previously buffered echo (and other ldisc-generated)
|
|
|
|
* characters to the tty.
|
|
|
|
*
|
|
|
|
* Characters generated by the ldisc (including echoes) need to
|
|
|
|
* be buffered because the driver's write buffer can fill during
|
|
|
|
* heavy program output. Echoing straight to the driver will
|
|
|
|
* often fail under these conditions, causing lost characters and
|
|
|
|
* resulting mismatches of ldisc state information.
|
|
|
|
*
|
|
|
|
* Since the ldisc state must represent the characters actually sent
|
|
|
|
* to the driver at the time of the write, operations like certain
|
|
|
|
* changes in column state are also saved in the buffer and executed
|
|
|
|
* here.
|
|
|
|
*
|
|
|
|
* A circular fifo buffer is used so that the most recent characters
|
|
|
|
* are prioritized. Also, when control characters are echoed with a
|
|
|
|
* prefixed "^", the pair is treated atomically and thus not separated.
|
|
|
|
*
|
2013-06-15 21:04:25 +07:00
|
|
|
* Locking: callers must hold output_lock
|
2009-01-02 20:40:53 +07:00
|
|
|
*/
|
|
|
|
|
2013-06-15 21:04:27 +07:00
|
|
|
static size_t __process_echoes(struct tty_struct *tty)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-06-15 21:04:27 +07:00
|
|
|
int space, old_space;
|
2013-06-15 21:04:22 +07:00
|
|
|
size_t tail;
|
2009-01-02 20:40:53 +07:00
|
|
|
unsigned char c;
|
|
|
|
|
2013-06-15 21:04:27 +07:00
|
|
|
old_space = space = tty_write_room(tty);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2013-06-15 21:04:22 +07:00
|
|
|
tail = ldata->echo_tail;
|
2013-06-15 21:04:28 +07:00
|
|
|
while (ldata->echo_commit != tail) {
|
2013-06-15 21:04:22 +07:00
|
|
|
c = echo_buf(ldata, tail);
|
2009-01-02 20:40:53 +07:00
|
|
|
if (c == ECHO_OP_START) {
|
|
|
|
unsigned char op;
|
|
|
|
int no_space_left = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the buffer byte is the start of a multi-byte
|
|
|
|
* operation, get the next byte, which is either the
|
|
|
|
* op code or a control character value.
|
|
|
|
*/
|
2013-06-15 21:04:22 +07:00
|
|
|
op = echo_buf(ldata, tail + 1);
|
2009-01-02 20:41:04 +07:00
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
switch (op) {
|
|
|
|
unsigned int num_chars, num_bs;
|
|
|
|
|
|
|
|
case ECHO_OP_ERASE_TAB:
|
2013-06-15 21:04:22 +07:00
|
|
|
num_chars = echo_buf(ldata, tail + 2);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Determine how many columns to go back
|
|
|
|
* in order to erase the tab.
|
|
|
|
* This depends on the number of columns
|
|
|
|
* used by other characters within the tab
|
|
|
|
* area. If this (modulo 8) count is from
|
|
|
|
* the start of input rather than from a
|
|
|
|
* previous tab, we offset by canon column.
|
|
|
|
* Otherwise, tab spacing is normal.
|
|
|
|
*/
|
|
|
|
if (!(num_chars & 0x80))
|
2012-10-19 03:26:41 +07:00
|
|
|
num_chars += ldata->canon_column;
|
2009-01-02 20:40:53 +07:00
|
|
|
num_bs = 8 - (num_chars & 7);
|
|
|
|
|
|
|
|
if (num_bs > space) {
|
|
|
|
no_space_left = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
space -= num_bs;
|
|
|
|
while (num_bs--) {
|
|
|
|
tty_put_char(tty, '\b');
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->column > 0)
|
|
|
|
ldata->column--;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 3;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ECHO_OP_SET_CANON_COL:
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->canon_column = ldata->column;
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 2;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ECHO_OP_MOVE_BACK_COL:
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->column > 0)
|
|
|
|
ldata->column--;
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 2;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ECHO_OP_START:
|
|
|
|
/* This is an escaped echo op start code */
|
|
|
|
if (!space) {
|
|
|
|
no_space_left = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tty_put_char(tty, ECHO_OP_START);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column++;
|
2009-01-02 20:40:53 +07:00
|
|
|
space--;
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 2;
|
2009-01-02 20:40:53 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/*
|
2009-09-10 04:03:47 +07:00
|
|
|
* If the op is not a special byte code,
|
|
|
|
* it is a ctrl char tagged to be echoed
|
|
|
|
* as "^X" (where X is the letter
|
|
|
|
* representing the control char).
|
|
|
|
* Note that we must ensure there is
|
|
|
|
* enough space for the whole ctrl pair.
|
|
|
|
*
|
2009-01-02 20:40:53 +07:00
|
|
|
*/
|
2009-09-10 04:03:47 +07:00
|
|
|
if (space < 2) {
|
|
|
|
no_space_left = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tty_put_char(tty, '^');
|
|
|
|
tty_put_char(tty, op ^ 0100);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column += 2;
|
2009-09-10 04:03:47 +07:00
|
|
|
space -= 2;
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 2;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (no_space_left)
|
|
|
|
break;
|
|
|
|
} else {
|
2013-05-17 23:49:48 +07:00
|
|
|
if (O_OPOST(tty)) {
|
2009-09-10 04:03:13 +07:00
|
|
|
int retval = do_output_char(c, tty, space);
|
|
|
|
if (retval < 0)
|
|
|
|
break;
|
|
|
|
space -= retval;
|
|
|
|
} else {
|
|
|
|
if (!space)
|
|
|
|
break;
|
|
|
|
tty_put_char(tty, c);
|
|
|
|
space -= 1;
|
|
|
|
}
|
2013-06-15 21:04:22 +07:00
|
|
|
tail += 1;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:04:26 +07:00
|
|
|
/* If the echo buffer is nearly full (so that the possibility exists
|
|
|
|
* of echo overrun before the next commit), then discard enough
|
|
|
|
* data at the tail to prevent a subsequent overrun */
|
|
|
|
while (ldata->echo_commit - tail >= ECHO_DISCARD_WATERMARK) {
|
2013-10-12 03:08:49 +07:00
|
|
|
if (echo_buf(ldata, tail) == ECHO_OP_START) {
|
2013-11-08 21:42:18 +07:00
|
|
|
if (echo_buf(ldata, tail + 1) == ECHO_OP_ERASE_TAB)
|
2013-06-15 21:04:26 +07:00
|
|
|
tail += 3;
|
|
|
|
else
|
|
|
|
tail += 2;
|
|
|
|
} else
|
|
|
|
tail++;
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:04:22 +07:00
|
|
|
ldata->echo_tail = tail;
|
2013-06-15 21:04:27 +07:00
|
|
|
return old_space - space;
|
2013-06-15 21:04:25 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void commit_echoes(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-06-15 21:04:27 +07:00
|
|
|
size_t nr, old, echoed;
|
2013-06-15 21:04:26 +07:00
|
|
|
size_t head;
|
|
|
|
|
|
|
|
head = ldata->echo_head;
|
2013-12-10 06:06:07 +07:00
|
|
|
ldata->echo_mark = head;
|
2013-06-15 21:04:26 +07:00
|
|
|
old = ldata->echo_commit - ldata->echo_tail;
|
|
|
|
|
|
|
|
/* Process committed echoes if the accumulated # of bytes
|
|
|
|
* is over the threshold (and try again each time another
|
|
|
|
* block is accumulated) */
|
|
|
|
nr = head - ldata->echo_tail;
|
|
|
|
if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK))
|
|
|
|
return;
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2013-06-15 21:04:25 +07:00
|
|
|
mutex_lock(&ldata->output_lock);
|
2013-06-15 21:04:26 +07:00
|
|
|
ldata->echo_commit = head;
|
2013-06-15 21:04:27 +07:00
|
|
|
echoed = __process_echoes(tty);
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2013-06-15 21:04:27 +07:00
|
|
|
if (echoed && tty->ops->flush_chars)
|
2009-01-02 20:40:53 +07:00
|
|
|
tty->ops->flush_chars(tty);
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:04:25 +07:00
|
|
|
static void process_echoes(struct tty_struct *tty)
|
2013-06-15 21:04:24 +07:00
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-06-15 21:04:27 +07:00
|
|
|
size_t echoed;
|
2013-06-15 21:04:24 +07:00
|
|
|
|
2014-02-12 04:34:55 +07:00
|
|
|
if (ldata->echo_mark == ldata->echo_tail)
|
2013-06-15 21:04:25 +07:00
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&ldata->output_lock);
|
2013-12-10 06:06:07 +07:00
|
|
|
ldata->echo_commit = ldata->echo_mark;
|
2013-06-15 21:04:27 +07:00
|
|
|
echoed = __process_echoes(tty);
|
2013-06-15 21:04:25 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
|
2013-06-15 21:04:27 +07:00
|
|
|
if (echoed && tty->ops->flush_chars)
|
2013-06-15 21:04:25 +07:00
|
|
|
tty->ops->flush_chars(tty);
|
2013-06-15 21:04:24 +07:00
|
|
|
}
|
|
|
|
|
2013-12-10 06:06:07 +07:00
|
|
|
/* NB: echo_mark and echo_head should be equivalent here */
|
2013-06-15 21:04:26 +07:00
|
|
|
static void flush_echoes(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2013-11-30 00:56:10 +07:00
|
|
|
if ((!L_ECHO(tty) && !L_ECHONL(tty)) ||
|
|
|
|
ldata->echo_commit == ldata->echo_head)
|
2013-06-15 21:04:26 +07:00
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&ldata->output_lock);
|
|
|
|
ldata->echo_commit = ldata->echo_head;
|
|
|
|
__process_echoes(tty);
|
|
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
}
|
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
/**
|
|
|
|
* add_echo_byte - add a byte to the echo buffer
|
|
|
|
* @c: unicode byte to echo
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Add a character or operation byte to the echo buffer.
|
|
|
|
*/
|
|
|
|
|
2013-06-15 21:04:26 +07:00
|
|
|
static inline void add_echo_byte(unsigned char c, struct n_tty_data *ldata)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
2013-06-15 21:04:22 +07:00
|
|
|
*echo_buf_addr(ldata, ldata->echo_head++) = c;
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* echo_move_back_col - add operation to move back a column
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Add an operation to the echo buffer to move back one column.
|
|
|
|
*/
|
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
static void echo_move_back_col(struct n_tty_data *ldata)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(ECHO_OP_MOVE_BACK_COL, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* echo_set_canon_col - add operation to set the canon column
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Add an operation to the echo buffer to set the canon column
|
|
|
|
* to the current column.
|
|
|
|
*/
|
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
static void echo_set_canon_col(struct n_tty_data *ldata)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(ECHO_OP_SET_CANON_COL, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* echo_erase_tab - add operation to erase a tab
|
|
|
|
* @num_chars: number of character columns already used
|
|
|
|
* @after_tab: true if num_chars starts after a previous tab
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Add an operation to the echo buffer to erase a tab.
|
|
|
|
*
|
|
|
|
* Called by the eraser function, which knows how many character
|
|
|
|
* columns have been used since either a previous tab or the start
|
|
|
|
* of input. This information will be used later, along with
|
|
|
|
* canon column (if applicable), to go back the correct number
|
|
|
|
* of columns.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void echo_erase_tab(unsigned int num_chars, int after_tab,
|
2012-10-19 03:26:43 +07:00
|
|
|
struct n_tty_data *ldata)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(ECHO_OP_ERASE_TAB, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
|
|
|
|
/* We only need to know this modulo 8 (tab spacing) */
|
|
|
|
num_chars &= 7;
|
|
|
|
|
|
|
|
/* Set the high bit as a flag if num_chars is after a previous tab */
|
|
|
|
if (after_tab)
|
|
|
|
num_chars |= 0x80;
|
2009-01-02 20:41:04 +07:00
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(num_chars, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* echo_char_raw - echo a character raw
|
|
|
|
* @c: unicode byte to echo
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
|
|
|
* Echo user input back onto the screen. This must be called only when
|
|
|
|
* L_ECHO(tty) is true. Called from the driver receive_buf path.
|
|
|
|
*
|
|
|
|
* This variant does not treat control characters specially.
|
|
|
|
*/
|
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
static void echo_char_raw(unsigned char c, struct n_tty_data *ldata)
|
2009-01-02 20:40:53 +07:00
|
|
|
{
|
|
|
|
if (c == ECHO_OP_START) {
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
} else {
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(c, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
2009-01-02 20:40:53 +07:00
|
|
|
* echo_char - echo a character
|
2005-04-17 05:20:36 +07:00
|
|
|
* @c: unicode byte to echo
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
2008-02-08 19:18:44 +07:00
|
|
|
* Echo user input back onto the screen. This must be called only when
|
2005-04-17 05:20:36 +07:00
|
|
|
* L_ECHO(tty) is true. Called from the driver receive_buf path.
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2009-09-10 04:03:47 +07:00
|
|
|
* This variant tags control characters to be echoed as "^X"
|
|
|
|
* (where X is the letter representing the control char).
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
|
|
|
|
static void echo_char(unsigned char c, struct tty_struct *tty)
|
|
|
|
{
|
2012-10-19 03:26:42 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
if (c == ECHO_OP_START) {
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
} else {
|
2009-09-10 04:03:47 +07:00
|
|
|
if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t')
|
2012-10-19 03:26:43 +07:00
|
|
|
add_echo_byte(ECHO_OP_START, ldata);
|
|
|
|
add_echo_byte(c, ldata);
|
2009-01-02 20:40:53 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2008-10-13 16:45:06 +07:00
|
|
|
/**
|
2009-01-02 20:40:53 +07:00
|
|
|
* finish_erasing - complete erase
|
2012-10-19 03:26:43 +07:00
|
|
|
* @ldata: n_tty data
|
2008-10-13 16:45:06 +07:00
|
|
|
*/
|
2009-01-02 20:40:53 +07:00
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
static inline void finish_erasing(struct n_tty_data *ldata)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->erasing) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('/', ldata);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->erasing = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* eraser - handle erase function
|
|
|
|
* @c: character input
|
|
|
|
* @tty: terminal device
|
|
|
|
*
|
2007-10-20 04:10:43 +07:00
|
|
|
* Perform erase and necessary output when an erase character is
|
2005-04-17 05:20:36 +07:00
|
|
|
* present in the stream from the driver layer. Handles the complexities
|
|
|
|
* of UTF-8 multibyte symbols.
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static void eraser(unsigned char c, struct tty_struct *tty)
|
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
enum { ERASE, WERASE, KILL } kill_type;
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t head;
|
|
|
|
size_t cnt;
|
|
|
|
int seen_alnums;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-10-19 03:26:41 +07:00
|
|
|
if (ldata->read_head == ldata->canon_head) {
|
2009-01-02 20:43:40 +07:00
|
|
|
/* process_output('\a', tty); */ /* what do you think? */
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (c == ERASE_CHAR(tty))
|
|
|
|
kill_type = ERASE;
|
|
|
|
else if (c == WERASE_CHAR(tty))
|
|
|
|
kill_type = WERASE;
|
|
|
|
else {
|
|
|
|
if (!L_ECHO(tty)) {
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->read_head = ldata->canon_head;
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) {
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->read_head = ldata->canon_head;
|
2012-10-19 03:26:43 +07:00
|
|
|
finish_erasing(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
echo_char(KILL_CHAR(tty), tty);
|
|
|
|
/* Add a newline if ECHOK is on and ECHOKE is off. */
|
|
|
|
if (L_ECHOK(tty))
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\n', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
kill_type = KILL;
|
|
|
|
}
|
|
|
|
|
|
|
|
seen_alnums = 0;
|
2012-10-19 03:26:41 +07:00
|
|
|
while (ldata->read_head != ldata->canon_head) {
|
|
|
|
head = ldata->read_head;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* erase a single possibly multibyte character */
|
|
|
|
do {
|
2013-06-15 20:14:21 +07:00
|
|
|
head--;
|
|
|
|
c = read_buf(ldata, head);
|
2012-10-19 03:26:41 +07:00
|
|
|
} while (is_continuation(c, tty) && head != ldata->canon_head);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/* do not partially erase */
|
|
|
|
if (is_continuation(c, tty))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (kill_type == WERASE) {
|
|
|
|
/* Equivalent to BSD's ALTWERASE. */
|
|
|
|
if (isalnum(c) || c == '_')
|
|
|
|
seen_alnums++;
|
|
|
|
else if (seen_alnums)
|
|
|
|
break;
|
|
|
|
}
|
2013-06-15 20:14:21 +07:00
|
|
|
cnt = ldata->read_head - head;
|
2012-10-19 03:26:41 +07:00
|
|
|
ldata->read_head = head;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ECHO(tty)) {
|
|
|
|
if (L_ECHOPRT(tty)) {
|
2012-10-19 03:26:39 +07:00
|
|
|
if (!ldata->erasing) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\\', ldata);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->erasing = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/* if cnt > 1, output a multi-byte character */
|
|
|
|
echo_char(c, tty);
|
|
|
|
while (--cnt > 0) {
|
2013-06-15 20:14:21 +07:00
|
|
|
head++;
|
|
|
|
echo_char_raw(read_buf(ldata, head), ldata);
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_move_back_col(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
} else if (kill_type == ERASE && !L_ECHOE(tty)) {
|
|
|
|
echo_char(ERASE_CHAR(tty), tty);
|
|
|
|
} else if (c == '\t') {
|
2009-01-02 20:40:53 +07:00
|
|
|
unsigned int num_chars = 0;
|
|
|
|
int after_tab = 0;
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t tail = ldata->read_head;
|
2009-01-02 20:40:53 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Count the columns used for characters
|
|
|
|
* since the start of input or after a
|
|
|
|
* previous tab.
|
|
|
|
* This info is used to go back the correct
|
|
|
|
* number of columns.
|
|
|
|
*/
|
2012-10-19 03:26:41 +07:00
|
|
|
while (tail != ldata->canon_head) {
|
2013-06-15 20:14:21 +07:00
|
|
|
tail--;
|
|
|
|
c = read_buf(ldata, tail);
|
2009-01-02 20:40:53 +07:00
|
|
|
if (c == '\t') {
|
|
|
|
after_tab = 1;
|
|
|
|
break;
|
2009-01-02 20:41:04 +07:00
|
|
|
} else if (iscntrl(c)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ECHOCTL(tty))
|
2009-01-02 20:40:53 +07:00
|
|
|
num_chars += 2;
|
|
|
|
} else if (!is_continuation(c, tty)) {
|
|
|
|
num_chars++;
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_erase_tab(num_chars, after_tab, ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
|
|
|
if (iscntrl(c) && L_ECHOCTL(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\b', ldata);
|
|
|
|
echo_char_raw(' ', ldata);
|
|
|
|
echo_char_raw('\b', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
if (!iscntrl(c) || L_ECHOCTL(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\b', ldata);
|
|
|
|
echo_char_raw(' ', ldata);
|
|
|
|
echo_char_raw('\b', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (kill_type == ERASE)
|
|
|
|
break;
|
|
|
|
}
|
2012-10-19 03:26:41 +07:00
|
|
|
if (ldata->read_head == ldata->canon_head && L_ECHO(tty))
|
2012-10-19 03:26:43 +07:00
|
|
|
finish_erasing(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* isig - handle the ISIG optio
|
|
|
|
* @sig: signal
|
|
|
|
* @tty: terminal
|
|
|
|
*
|
2013-03-06 20:38:19 +07:00
|
|
|
* Called when a signal is being sent due to terminal input.
|
|
|
|
* Called from the driver receive_buf path so serialized.
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2015-01-18 03:42:06 +07:00
|
|
|
* Performs input and output flush if !NOFLSH. In this context, the echo
|
|
|
|
* buffer is 'output'. The signal is processed first to alert any current
|
|
|
|
* readers or writers to discontinue and exit their i/o loops.
|
|
|
|
*
|
2013-03-06 20:38:19 +07:00
|
|
|
* Locking: ctrl_lock
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2015-06-27 20:21:32 +07:00
|
|
|
static void __isig(int sig, struct tty_struct *tty)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-03-06 20:38:19 +07:00
|
|
|
struct pid *tty_pgrp = tty_get_pgrp(tty);
|
|
|
|
if (tty_pgrp) {
|
|
|
|
kill_pgrp(tty_pgrp, sig, 1);
|
|
|
|
put_pid(tty_pgrp);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2015-06-27 20:21:32 +07:00
|
|
|
}
|
2015-01-18 03:42:06 +07:00
|
|
|
|
2015-06-27 20:21:32 +07:00
|
|
|
static void isig(int sig, struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
|
|
|
if (L_NOFLSH(tty)) {
|
|
|
|
/* signal only */
|
|
|
|
__isig(sig, tty);
|
|
|
|
|
|
|
|
} else { /* signal and flush */
|
2015-01-18 03:42:06 +07:00
|
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
down_write(&tty->termios_rwsem);
|
|
|
|
|
2015-06-27 20:21:32 +07:00
|
|
|
__isig(sig, tty);
|
|
|
|
|
2015-01-18 03:42:06 +07:00
|
|
|
/* clear echo buffer */
|
|
|
|
mutex_lock(&ldata->output_lock);
|
|
|
|
ldata->echo_head = ldata->echo_tail = 0;
|
|
|
|
ldata->echo_mark = ldata->echo_commit = 0;
|
|
|
|
mutex_unlock(&ldata->output_lock);
|
|
|
|
|
|
|
|
/* clear output buffer */
|
|
|
|
tty_driver_flush_buffer(tty);
|
|
|
|
|
|
|
|
/* clear input buffer */
|
|
|
|
reset_buffer_flags(tty->disc_data);
|
|
|
|
|
|
|
|
/* notify pty master of flush */
|
|
|
|
if (tty->link)
|
|
|
|
n_tty_packet_mode_flush(tty);
|
|
|
|
|
|
|
|
up_write(&tty->termios_rwsem);
|
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_receive_break - handle break
|
|
|
|
* @tty: terminal
|
|
|
|
*
|
|
|
|
* An RS232 break event has been hit in the incoming bitstream. This
|
|
|
|
* can cause a variety of events depending upon the termios settings.
|
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
|
|
|
*
|
|
|
|
* Note: may get exclusive termios_rwsem if flushing input buffer
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2013-07-24 19:29:55 +07:00
|
|
|
static void n_tty_receive_break(struct tty_struct *tty)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:43 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (I_IGNBRK(tty))
|
|
|
|
return;
|
|
|
|
if (I_BRKINT(tty)) {
|
2013-03-06 20:38:19 +07:00
|
|
|
isig(SIGINT, tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (I_PARMRK(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue('\377', ldata);
|
|
|
|
put_tty_queue('\0', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue('\0', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_receive_overrun - handle overrun reporting
|
|
|
|
* @tty: terminal
|
|
|
|
*
|
|
|
|
* Data arrived faster than we could process it. While the tty
|
|
|
|
* driver has flagged this the bits that were missed are gone
|
|
|
|
* forever.
|
|
|
|
*
|
|
|
|
* Called from the receive_buf path so single threaded. Does not
|
|
|
|
* need locking as num_overrun and overrun_time are function
|
|
|
|
* private.
|
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2013-07-24 19:29:55 +07:00
|
|
|
static void n_tty_receive_overrun(struct tty_struct *tty)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->num_overrun++;
|
|
|
|
if (time_after(jiffies, ldata->overrun_time + HZ) ||
|
|
|
|
time_after(ldata->overrun_time, jiffies)) {
|
2015-11-09 01:01:13 +07:00
|
|
|
tty_warn(tty, "%d input overrun(s)\n", ldata->num_overrun);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->overrun_time = jiffies;
|
|
|
|
ldata->num_overrun = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_receive_parity_error - error notifier
|
|
|
|
* @tty: terminal device
|
|
|
|
* @c: character
|
|
|
|
*
|
|
|
|
* Process a parity error and queue the right data to indicate
|
2013-06-15 20:14:26 +07:00
|
|
|
* the error case if necessary.
|
|
|
|
*
|
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2013-07-24 19:29:55 +07:00
|
|
|
static void n_tty_receive_parity_error(struct tty_struct *tty, unsigned char c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:43 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
tty: Correct INPCK handling
If INPCK is not set, input parity detection should be disabled. This means
parity errors should not be received from the tty driver, and the data
received should be treated normally.
SUS v3, 11.2.2, General Terminal Interface - Input Modes, states:
"If INPCK is set, input parity checking shall be enabled. If INPCK is
not set, input parity checking shall be disabled, allowing output parity
generation without input parity errors. Note that whether input parity
checking is enabled or disabled is independent of whether parity detection
is enabled or disabled (see Control Modes). If parity detection is enabled
but input parity checking is disabled, the hardware to which the terminal
is connected shall recognize the parity bit, but the terminal special file
shall not check whether or not this bit is correctly set."
Ignore parity errors reported by the tty driver when INPCK is not set, and
handle the received data normally.
Fixes: Bugzilla #71681, 'Improvement of n_tty_receive_parity_error from n_tty.c'
Reported-by: Ivan <athlon_@mail.ru>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2014-06-16 19:10:42 +07:00
|
|
|
if (I_INPCK(tty)) {
|
|
|
|
if (I_IGNPAR(tty))
|
|
|
|
return;
|
|
|
|
if (I_PARMRK(tty)) {
|
|
|
|
put_tty_queue('\377', ldata);
|
|
|
|
put_tty_queue('\0', ldata);
|
|
|
|
put_tty_queue(c, ldata);
|
|
|
|
} else
|
|
|
|
put_tty_queue('\0', ldata);
|
|
|
|
} else
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue(c, ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:23 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_signal_char(struct tty_struct *tty, int signal, unsigned char c)
|
|
|
|
{
|
2015-01-18 03:42:06 +07:00
|
|
|
isig(signal, tty);
|
2013-06-15 21:21:23 +07:00
|
|
|
if (I_IXON(tty))
|
|
|
|
start_tty(tty);
|
|
|
|
if (L_ECHO(tty)) {
|
|
|
|
echo_char(c, tty);
|
|
|
|
commit_echoes(tty);
|
2014-02-12 04:34:55 +07:00
|
|
|
} else
|
|
|
|
process_echoes(tty);
|
2013-06-15 21:21:23 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
|
|
|
* n_tty_receive_char - perform processing
|
|
|
|
* @tty: terminal device
|
|
|
|
* @c: character
|
|
|
|
*
|
|
|
|
* Process an individual character of input received from the driver.
|
2008-02-08 19:18:44 +07:00
|
|
|
* This is serialized with respect to itself by the rules for the
|
2005-04-17 05:20:36 +07:00
|
|
|
* driver above.
|
2013-06-15 20:14:26 +07:00
|
|
|
*
|
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
|
|
|
* publishes canon_head if canonical mode is active
|
2013-07-24 19:29:56 +07:00
|
|
|
*
|
|
|
|
* Returns 1 if LNEXT was received, else returns 0
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
|
|
|
|
2013-07-24 19:29:56 +07:00
|
|
|
static int
|
2013-07-24 19:29:51 +07:00
|
|
|
n_tty_receive_char_special(struct tty_struct *tty, unsigned char c)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (I_IXON(tty)) {
|
|
|
|
if (c == START_CHAR(tty)) {
|
|
|
|
start_tty(tty);
|
2014-02-12 04:34:55 +07:00
|
|
|
process_echoes(tty);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
if (c == STOP_CHAR(tty)) {
|
|
|
|
stop_tty(tty);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2008-04-30 14:53:30 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ISIG(tty)) {
|
2013-06-15 21:21:23 +07:00
|
|
|
if (c == INTR_CHAR(tty)) {
|
|
|
|
n_tty_receive_signal_char(tty, SIGINT, c);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2013-06-15 21:21:23 +07:00
|
|
|
} else if (c == QUIT_CHAR(tty)) {
|
|
|
|
n_tty_receive_signal_char(tty, SIGQUIT, c);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2013-06-15 21:21:23 +07:00
|
|
|
} else if (c == SUSP_CHAR(tty)) {
|
|
|
|
n_tty_receive_signal_char(tty, SIGTSTP, c);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2008-04-30 14:53:30 +07:00
|
|
|
|
2013-07-24 19:29:50 +07:00
|
|
|
if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) {
|
|
|
|
start_tty(tty);
|
|
|
|
process_echoes(tty);
|
|
|
|
}
|
|
|
|
|
2008-04-30 14:53:30 +07:00
|
|
|
if (c == '\r') {
|
|
|
|
if (I_IGNCR(tty))
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2008-04-30 14:53:30 +07:00
|
|
|
if (I_ICRNL(tty))
|
|
|
|
c = '\n';
|
|
|
|
} else if (c == '\n' && I_INLCR(tty))
|
|
|
|
c = '\r';
|
|
|
|
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->icanon) {
|
2005-04-17 05:20:36 +07:00
|
|
|
if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) ||
|
|
|
|
(c == WERASE_CHAR(tty) && L_IEXTEN(tty))) {
|
|
|
|
eraser(c, tty);
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) {
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->lnext = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ECHO(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
finish_erasing(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ECHOCTL(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('^', ldata);
|
|
|
|
echo_char_raw('\b', ldata);
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2013-07-24 19:29:56 +07:00
|
|
|
return 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2013-07-24 19:29:56 +07:00
|
|
|
if (c == REPRINT_CHAR(tty) && L_ECHO(tty) && L_IEXTEN(tty)) {
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t tail = ldata->canon_head;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
finish_erasing(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
echo_char(c, tty);
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\n', ldata);
|
2012-10-19 03:26:41 +07:00
|
|
|
while (tail != ldata->read_head) {
|
2013-06-15 20:14:21 +07:00
|
|
|
echo_char(read_buf(ldata, tail), tty);
|
|
|
|
tail++;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
if (c == '\n') {
|
2009-01-02 20:43:32 +07:00
|
|
|
if (L_ECHO(tty) || L_ECHONL(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\n', ldata);
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
goto handle_newline;
|
|
|
|
}
|
|
|
|
if (c == EOF_CHAR(tty)) {
|
|
|
|
c = __DISABLED_CHAR;
|
|
|
|
goto handle_newline;
|
|
|
|
}
|
|
|
|
if ((c == EOL_CHAR(tty)) ||
|
|
|
|
(c == EOL2_CHAR(tty) && L_IEXTEN(tty))) {
|
|
|
|
/*
|
|
|
|
* XXX are EOL_CHAR and EOL2_CHAR echoed?!?
|
|
|
|
*/
|
|
|
|
if (L_ECHO(tty)) {
|
|
|
|
/* Record the column of first canon char. */
|
2012-10-19 03:26:41 +07:00
|
|
|
if (ldata->canon_head == ldata->read_head)
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_set_canon_col(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
echo_char(c, tty);
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
/*
|
|
|
|
* XXX does PARMRK doubling happen for
|
|
|
|
* EOL_CHAR and EOL2_CHAR?
|
|
|
|
*/
|
2013-12-03 02:24:44 +07:00
|
|
|
if (c == (unsigned char) '\377' && I_PARMRK(tty))
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue(c, ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2008-02-08 19:18:44 +07:00
|
|
|
handle_newline:
|
2013-06-15 20:14:21 +07:00
|
|
|
set_bit(ldata->read_head & (N_TTY_BUF_SIZE - 1), ldata->read_flags);
|
2013-06-15 20:14:26 +07:00
|
|
|
put_tty_queue(c, ldata);
|
2015-01-17 03:05:37 +07:00
|
|
|
smp_store_release(&ldata->canon_head, ldata->read_head);
|
2005-04-17 05:20:36 +07:00
|
|
|
kill_fasync(&tty->fasync, SIGIO, POLL_IN);
|
tty: fix stall caused by missing memory barrier in drivers/tty/n_tty.c
My colleague ran into a program stall on a x86_64 server, where
n_tty_read() was waiting for data even if there was data in the buffer
in the pty. kernel stack for the stuck process looks like below.
#0 [ffff88303d107b58] __schedule at ffffffff815c4b20
#1 [ffff88303d107bd0] schedule at ffffffff815c513e
#2 [ffff88303d107bf0] schedule_timeout at ffffffff815c7818
#3 [ffff88303d107ca0] wait_woken at ffffffff81096bd2
#4 [ffff88303d107ce0] n_tty_read at ffffffff8136fa23
#5 [ffff88303d107dd0] tty_read at ffffffff81368013
#6 [ffff88303d107e20] __vfs_read at ffffffff811a3704
#7 [ffff88303d107ec0] vfs_read at ffffffff811a3a57
#8 [ffff88303d107f00] sys_read at ffffffff811a4306
#9 [ffff88303d107f50] entry_SYSCALL_64_fastpath at ffffffff815c86d7
There seems to be two problems causing this issue.
First, in drivers/tty/n_tty.c, __receive_buf() stores the data and
updates ldata->commit_head using smp_store_release() and then checks
the wait queue using waitqueue_active(). However, since there is no
memory barrier, __receive_buf() could return without calling
wake_up_interactive_poll(), and at the same time, n_tty_read() could
start to wait in wait_woken() as in the following chart.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
if (waitqueue_active(&tty->read_wait))
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
add_wait_queue(&tty->read_wait, &wait);
...
if (!input_available_p(tty, 0)) {
smp_store_release(&ldata->commit_head,
ldata->read_head);
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
The second problem is that n_tty_read() also lacks a memory barrier
call and could also cause __receive_buf() to return without calling
wake_up_interactive_poll(), and n_tty_read() to wait in wait_woken()
as in the chart below.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
spin_lock_irqsave(&q->lock, flags);
/* from add_wait_queue() */
...
if (!input_available_p(tty, 0)) {
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
smp_store_release(&ldata->commit_head,
ldata->read_head);
if (waitqueue_active(&tty->read_wait))
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock,flags);
/* from add_wait_queue() */
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
There are also other places in drivers/tty/n_tty.c which have similar
calls to waitqueue_active(), so instead of adding many memory barrier
calls, this patch simply removes the call to waitqueue_active(),
leaving just wake_up*() behind.
This fixes both problems because, even though the memory access before
or after the spinlocks in both wake_up*() and add_wait_queue() can
sneak into the critical section, it cannot go past it and the critical
section assures that they will be serialized (please see "INTER-CPU
ACQUIRING BARRIER EFFECTS" in Documentation/memory-barriers.txt for a
better explanation). Moreover, the resulting code is much simpler.
Latency measurement using a ping-pong test over a pty doesn't show any
visible performance drop.
Signed-off-by: Kosuke Tatsukawa <tatsu@ab.jp.nec.com>
Cc: stable@vger.kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-10-02 15:27:05 +07:00
|
|
|
wake_up_interruptible_poll(&tty->read_wait, POLLIN);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2009-01-02 20:43:32 +07:00
|
|
|
if (L_ECHO(tty)) {
|
2012-10-19 03:26:43 +07:00
|
|
|
finish_erasing(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (c == '\n')
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_char_raw('\n', ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
else {
|
|
|
|
/* Record the column of first canon char. */
|
2012-10-19 03:26:41 +07:00
|
|
|
if (ldata->canon_head == ldata->read_head)
|
2012-10-19 03:26:43 +07:00
|
|
|
echo_set_canon_col(ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
echo_char(c, tty);
|
|
|
|
}
|
2013-06-15 21:04:24 +07:00
|
|
|
commit_echoes(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-12-03 02:24:44 +07:00
|
|
|
/* PARMRK doubling check */
|
|
|
|
if (c == (unsigned char) '\377' && I_PARMRK(tty))
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue(c, ldata);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
put_tty_queue(c, ldata);
|
2013-07-24 19:29:56 +07:00
|
|
|
return 0;
|
2008-02-08 19:18:44 +07:00
|
|
|
}
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-07-24 19:29:56 +07:00
|
|
|
static inline void
|
|
|
|
n_tty_receive_char_inline(struct tty_struct *tty, unsigned char c)
|
2013-07-24 19:29:51 +07:00
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2013-07-24 19:29:56 +07:00
|
|
|
if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) {
|
|
|
|
start_tty(tty);
|
|
|
|
process_echoes(tty);
|
|
|
|
}
|
|
|
|
if (L_ECHO(tty)) {
|
|
|
|
finish_erasing(ldata);
|
|
|
|
/* Record the column of first canon char. */
|
|
|
|
if (ldata->canon_head == ldata->read_head)
|
|
|
|
echo_set_canon_col(ldata);
|
|
|
|
echo_char(c, tty);
|
|
|
|
commit_echoes(tty);
|
2013-07-24 19:29:51 +07:00
|
|
|
}
|
2013-12-03 02:24:44 +07:00
|
|
|
/* PARMRK doubling check */
|
|
|
|
if (c == (unsigned char) '\377' && I_PARMRK(tty))
|
2013-07-24 19:29:56 +07:00
|
|
|
put_tty_queue(c, ldata);
|
|
|
|
put_tty_queue(c, ldata);
|
|
|
|
}
|
2013-07-24 19:29:51 +07:00
|
|
|
|
2013-12-03 02:24:42 +07:00
|
|
|
static void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
|
2013-07-24 19:29:56 +07:00
|
|
|
{
|
|
|
|
n_tty_receive_char_inline(tty, c);
|
2013-07-24 19:29:51 +07:00
|
|
|
}
|
|
|
|
|
2013-07-24 19:29:53 +07:00
|
|
|
static inline void
|
|
|
|
n_tty_receive_char_fast(struct tty_struct *tty, unsigned char c)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2013-07-24 19:29:56 +07:00
|
|
|
if (tty->stopped && !tty->flow_stopped && I_IXON(tty) && I_IXANY(tty)) {
|
|
|
|
start_tty(tty);
|
|
|
|
process_echoes(tty);
|
2013-07-24 19:29:53 +07:00
|
|
|
}
|
2013-07-24 19:29:56 +07:00
|
|
|
if (L_ECHO(tty)) {
|
|
|
|
finish_erasing(ldata);
|
|
|
|
/* Record the column of first canon char. */
|
|
|
|
if (ldata->canon_head == ldata->read_head)
|
|
|
|
echo_set_canon_col(ldata);
|
|
|
|
echo_char(c, tty);
|
|
|
|
commit_echoes(tty);
|
|
|
|
}
|
|
|
|
put_tty_queue(c, ldata);
|
2013-07-24 19:29:53 +07:00
|
|
|
}
|
|
|
|
|
2013-12-03 02:24:43 +07:00
|
|
|
static void n_tty_receive_char_closing(struct tty_struct *tty, unsigned char c)
|
2013-06-15 21:21:27 +07:00
|
|
|
{
|
|
|
|
if (I_ISTRIP(tty))
|
|
|
|
c &= 0x7f;
|
|
|
|
if (I_IUCLC(tty) && L_IEXTEN(tty))
|
|
|
|
c = tolower(c);
|
|
|
|
|
|
|
|
if (I_IXON(tty)) {
|
|
|
|
if (c == STOP_CHAR(tty))
|
|
|
|
stop_tty(tty);
|
|
|
|
else if (c == START_CHAR(tty) ||
|
|
|
|
(tty->stopped && !tty->flow_stopped && I_IXANY(tty) &&
|
|
|
|
c != INTR_CHAR(tty) && c != QUIT_CHAR(tty) &&
|
|
|
|
c != SUSP_CHAR(tty))) {
|
|
|
|
start_tty(tty);
|
|
|
|
process_echoes(tty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:24 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_char_flagged(struct tty_struct *tty, unsigned char c, char flag)
|
|
|
|
{
|
|
|
|
switch (flag) {
|
|
|
|
case TTY_BREAK:
|
|
|
|
n_tty_receive_break(tty);
|
|
|
|
break;
|
|
|
|
case TTY_PARITY:
|
|
|
|
case TTY_FRAME:
|
|
|
|
n_tty_receive_parity_error(tty, c);
|
|
|
|
break;
|
|
|
|
case TTY_OVERRUN:
|
|
|
|
n_tty_receive_overrun(tty);
|
|
|
|
break;
|
|
|
|
default:
|
2015-11-09 01:01:13 +07:00
|
|
|
tty_err(tty, "unknown flag %d\n", flag);
|
2013-06-15 21:21:24 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-24 19:29:56 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_char_lnext(struct tty_struct *tty, unsigned char c, char flag)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
|
|
|
ldata->lnext = 0;
|
|
|
|
if (likely(flag == TTY_NORMAL)) {
|
|
|
|
if (I_ISTRIP(tty))
|
|
|
|
c &= 0x7f;
|
|
|
|
if (I_IUCLC(tty) && L_IEXTEN(tty))
|
|
|
|
c = tolower(c);
|
|
|
|
n_tty_receive_char(tty, c);
|
|
|
|
} else
|
|
|
|
n_tty_receive_char_flagged(tty, c, flag);
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:22 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_buf_real_raw(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
size_t n, head;
|
|
|
|
|
|
|
|
head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
|
2015-01-17 03:05:37 +07:00
|
|
|
n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
|
2013-06-15 21:21:22 +07:00
|
|
|
memcpy(read_buf_addr(ldata, head), cp, n);
|
|
|
|
ldata->read_head += n;
|
|
|
|
cp += n;
|
|
|
|
count -= n;
|
|
|
|
|
|
|
|
head = ldata->read_head & (N_TTY_BUF_SIZE - 1);
|
2015-01-17 03:05:37 +07:00
|
|
|
n = min_t(size_t, count, N_TTY_BUF_SIZE - head);
|
2013-06-15 21:21:22 +07:00
|
|
|
memcpy(read_buf_addr(ldata, head), cp, n);
|
|
|
|
ldata->read_head += n;
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:25 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_buf_raw(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
char flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
while (count--) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
if (likely(flag == TTY_NORMAL))
|
|
|
|
put_tty_queue(*cp++, ldata);
|
|
|
|
else
|
|
|
|
n_tty_receive_char_flagged(tty, *cp++, flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 21:21:27 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_buf_closing(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
char flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
while (count--) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
if (likely(flag == TTY_NORMAL))
|
|
|
|
n_tty_receive_char_closing(tty, *cp++);
|
|
|
|
else
|
|
|
|
n_tty_receive_char_flagged(tty, *cp++, flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-24 19:29:49 +07:00
|
|
|
static void
|
|
|
|
n_tty_receive_buf_standard(struct tty_struct *tty, const unsigned char *cp,
|
2013-07-24 19:29:52 +07:00
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
char flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
while (count--) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
if (likely(flag == TTY_NORMAL)) {
|
|
|
|
unsigned char c = *cp++;
|
|
|
|
|
|
|
|
if (I_ISTRIP(tty))
|
|
|
|
c &= 0x7f;
|
|
|
|
if (I_IUCLC(tty) && L_IEXTEN(tty))
|
|
|
|
c = tolower(c);
|
|
|
|
if (L_EXTPROC(tty)) {
|
|
|
|
put_tty_queue(c, ldata);
|
|
|
|
continue;
|
|
|
|
}
|
2013-07-24 19:29:56 +07:00
|
|
|
if (!test_bit(c, ldata->char_map))
|
|
|
|
n_tty_receive_char_inline(tty, c);
|
|
|
|
else if (n_tty_receive_char_special(tty, c) && count) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
n_tty_receive_char_lnext(tty, *cp++, flag);
|
|
|
|
count--;
|
|
|
|
}
|
2013-07-24 19:29:52 +07:00
|
|
|
} else
|
|
|
|
n_tty_receive_char_flagged(tty, *cp++, flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
n_tty_receive_buf_fast(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
2013-07-24 19:29:49 +07:00
|
|
|
{
|
2013-07-24 19:29:56 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-07-24 19:29:49 +07:00
|
|
|
char flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
while (count--) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
2013-07-24 19:29:56 +07:00
|
|
|
if (likely(flag == TTY_NORMAL)) {
|
|
|
|
unsigned char c = *cp++;
|
|
|
|
|
|
|
|
if (!test_bit(c, ldata->char_map))
|
|
|
|
n_tty_receive_char_fast(tty, c);
|
|
|
|
else if (n_tty_receive_char_special(tty, c) && count) {
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
n_tty_receive_char_lnext(tty, *cp++, flag);
|
|
|
|
count--;
|
|
|
|
}
|
|
|
|
} else
|
2013-07-24 19:29:49 +07:00
|
|
|
n_tty_receive_char_flagged(tty, *cp++, flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:15 +07:00
|
|
|
static void __receive_buf(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-06-15 21:21:26 +07:00
|
|
|
bool preops = I_ISTRIP(tty) || (I_IUCLC(tty) && L_IEXTEN(tty));
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-06-15 21:21:22 +07:00
|
|
|
if (ldata->real_raw)
|
|
|
|
n_tty_receive_buf_real_raw(tty, cp, fp, count);
|
2013-06-15 21:21:26 +07:00
|
|
|
else if (ldata->raw || (L_EXTPROC(tty) && !preops))
|
2013-06-15 21:21:25 +07:00
|
|
|
n_tty_receive_buf_raw(tty, cp, fp, count);
|
2013-06-15 21:21:27 +07:00
|
|
|
else if (tty->closing && !L_EXTPROC(tty))
|
|
|
|
n_tty_receive_buf_closing(tty, cp, fp, count);
|
2013-06-15 21:21:22 +07:00
|
|
|
else {
|
2013-07-24 19:29:56 +07:00
|
|
|
if (ldata->lnext) {
|
|
|
|
char flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
if (fp)
|
|
|
|
flag = *fp++;
|
|
|
|
n_tty_receive_char_lnext(tty, *cp++, flag);
|
|
|
|
count--;
|
|
|
|
}
|
|
|
|
|
2013-07-24 19:29:53 +07:00
|
|
|
if (!preops && !I_PARMRK(tty))
|
2013-07-24 19:29:52 +07:00
|
|
|
n_tty_receive_buf_fast(tty, cp, fp, count);
|
|
|
|
else
|
|
|
|
n_tty_receive_buf_standard(tty, cp, fp, count);
|
2013-06-15 21:04:26 +07:00
|
|
|
|
|
|
|
flush_echoes(tty);
|
2008-04-30 14:54:13 +07:00
|
|
|
if (tty->ops->flush_chars)
|
|
|
|
tty->ops->flush_chars(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2015-01-17 03:05:37 +07:00
|
|
|
if (ldata->icanon && !L_EXTPROC(tty))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* publish read_head to consumer */
|
|
|
|
smp_store_release(&ldata->commit_head, ldata->read_head);
|
|
|
|
|
|
|
|
if ((read_cnt(ldata) >= ldata->minimum_to_wake) || L_EXTPROC(tty)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
kill_fasync(&tty->fasync, SIGIO, POLL_IN);
|
tty: fix stall caused by missing memory barrier in drivers/tty/n_tty.c
My colleague ran into a program stall on a x86_64 server, where
n_tty_read() was waiting for data even if there was data in the buffer
in the pty. kernel stack for the stuck process looks like below.
#0 [ffff88303d107b58] __schedule at ffffffff815c4b20
#1 [ffff88303d107bd0] schedule at ffffffff815c513e
#2 [ffff88303d107bf0] schedule_timeout at ffffffff815c7818
#3 [ffff88303d107ca0] wait_woken at ffffffff81096bd2
#4 [ffff88303d107ce0] n_tty_read at ffffffff8136fa23
#5 [ffff88303d107dd0] tty_read at ffffffff81368013
#6 [ffff88303d107e20] __vfs_read at ffffffff811a3704
#7 [ffff88303d107ec0] vfs_read at ffffffff811a3a57
#8 [ffff88303d107f00] sys_read at ffffffff811a4306
#9 [ffff88303d107f50] entry_SYSCALL_64_fastpath at ffffffff815c86d7
There seems to be two problems causing this issue.
First, in drivers/tty/n_tty.c, __receive_buf() stores the data and
updates ldata->commit_head using smp_store_release() and then checks
the wait queue using waitqueue_active(). However, since there is no
memory barrier, __receive_buf() could return without calling
wake_up_interactive_poll(), and at the same time, n_tty_read() could
start to wait in wait_woken() as in the following chart.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
if (waitqueue_active(&tty->read_wait))
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
add_wait_queue(&tty->read_wait, &wait);
...
if (!input_available_p(tty, 0)) {
smp_store_release(&ldata->commit_head,
ldata->read_head);
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
The second problem is that n_tty_read() also lacks a memory barrier
call and could also cause __receive_buf() to return without calling
wake_up_interactive_poll(), and n_tty_read() to wait in wait_woken()
as in the chart below.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
spin_lock_irqsave(&q->lock, flags);
/* from add_wait_queue() */
...
if (!input_available_p(tty, 0)) {
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
smp_store_release(&ldata->commit_head,
ldata->read_head);
if (waitqueue_active(&tty->read_wait))
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock,flags);
/* from add_wait_queue() */
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
There are also other places in drivers/tty/n_tty.c which have similar
calls to waitqueue_active(), so instead of adding many memory barrier
calls, this patch simply removes the call to waitqueue_active(),
leaving just wake_up*() behind.
This fixes both problems because, even though the memory access before
or after the spinlocks in both wake_up*() and add_wait_queue() can
sneak into the critical section, it cannot go past it and the critical
section assures that they will be serialized (please see "INTER-CPU
ACQUIRING BARRIER EFFECTS" in Documentation/memory-barriers.txt for a
better explanation). Moreover, the resulting code is much simpler.
Latency measurement using a ping-pong test over a pty doesn't show any
visible performance drop.
Signed-off-by: Kosuke Tatsukawa <tatsu@ab.jp.nec.com>
Cc: stable@vger.kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-10-02 15:27:05 +07:00
|
|
|
wake_up_interruptible_poll(&tty->read_wait, POLLIN);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
/**
|
|
|
|
* n_tty_receive_buf_common - process input
|
|
|
|
* @tty: device to receive input
|
|
|
|
* @cp: input chars
|
|
|
|
* @fp: flags for each char (if NULL, all chars are TTY_NORMAL)
|
|
|
|
* @count: number of input chars in @cp
|
|
|
|
*
|
|
|
|
* Called by the terminal driver when a block of characters has
|
|
|
|
* been received. This function must be called from soft contexts
|
|
|
|
* not from interrupt context. The driver is responsible for making
|
|
|
|
* calls one at a time and in order (or using flush_to_ldisc)
|
|
|
|
*
|
|
|
|
* Returns the # of input chars from @cp which were processed.
|
|
|
|
*
|
|
|
|
* In canonical mode, the maximum line length is 4096 chars (including
|
|
|
|
* the line termination char); lines longer than 4096 chars are
|
|
|
|
* truncated. After 4095 chars, input data is still processed but
|
|
|
|
* not stored. Overflow processing ensures the tty can always
|
|
|
|
* receive more input until at least one line can be read.
|
|
|
|
*
|
|
|
|
* In non-canonical mode, the read buffer will only accept 4095 chars;
|
|
|
|
* this provides the necessary space for a newline char if the input
|
|
|
|
* mode is switched to canonical.
|
|
|
|
*
|
|
|
|
* Note it is possible for the read buffer to _contain_ 4096 chars
|
|
|
|
* in non-canonical mode: the read buffer could already contain the
|
|
|
|
* maximum canon line of 4096 chars when the mode is switched to
|
|
|
|
* non-canonical.
|
|
|
|
*
|
|
|
|
* n_tty_receive_buf()/producer path:
|
|
|
|
* claims non-exclusive termios_rwsem
|
|
|
|
* publishes commit_head or canon_head
|
|
|
|
*/
|
2013-12-03 02:24:41 +07:00
|
|
|
static int
|
|
|
|
n_tty_receive_buf_common(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count, int flow)
|
2013-06-15 20:14:15 +07:00
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
int room, n, rcvd = 0, overflow;
|
2013-06-15 20:14:15 +07:00
|
|
|
|
2013-06-15 20:14:24 +07:00
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
while (1) {
|
2015-01-17 03:05:37 +07:00
|
|
|
/*
|
2015-01-17 03:05:38 +07:00
|
|
|
* When PARMRK is set, each input char may take up to 3 chars
|
|
|
|
* in the read buf; reduce the buffer space avail by 3x
|
2015-01-17 03:05:37 +07:00
|
|
|
*
|
|
|
|
* If we are doing input canonicalization, and there are no
|
|
|
|
* pending newlines, let characters through without limit, so
|
|
|
|
* that erase characters will be handled. Other excess
|
|
|
|
* characters will be beeped.
|
|
|
|
*
|
|
|
|
* paired with store in *_copy_from_read_buf() -- guarantees
|
|
|
|
* the consumer has loaded the data in read_buf up to the new
|
|
|
|
* read_tail (so this producer will not overwrite unread data)
|
|
|
|
*/
|
|
|
|
size_t tail = smp_load_acquire(&ldata->read_tail);
|
|
|
|
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
room = N_TTY_BUF_SIZE - (ldata->read_head - tail);
|
2015-01-17 03:05:37 +07:00
|
|
|
if (I_PARMRK(tty))
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
room = (room + 2) / 3;
|
|
|
|
room--;
|
|
|
|
if (room <= 0) {
|
|
|
|
overflow = ldata->icanon && ldata->canon_head == tail;
|
|
|
|
if (overflow && room < 0)
|
|
|
|
ldata->read_head--;
|
|
|
|
room = overflow;
|
|
|
|
ldata->no_room = flow && !room;
|
|
|
|
} else
|
|
|
|
overflow = 0;
|
2015-01-17 03:05:37 +07:00
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
n = min(count, room);
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
if (!n)
|
2013-07-24 19:29:54 +07:00
|
|
|
break;
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
|
|
|
|
/* ignore parity errors if handling overflow */
|
|
|
|
if (!overflow || !fp || *fp != TTY_PARITY)
|
|
|
|
__receive_buf(tty, cp, fp, n);
|
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
cp += n;
|
|
|
|
if (fp)
|
|
|
|
fp += n;
|
|
|
|
count -= n;
|
|
|
|
rcvd += n;
|
2013-06-15 21:21:22 +07:00
|
|
|
}
|
2013-06-15 20:14:15 +07:00
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
tty->receive_room = room;
|
n_tty: Fix read buffer overwrite when no newline
In canon mode, the read buffer head will advance over the buffer tail
if the input > 4095 bytes without receiving a line termination char.
Discard additional input until a line termination is received.
Before evaluating for overflow, the 'room' value is normalized for
I_PARMRK and 1 byte is reserved for line termination (even in !icanon
mode, in case the mode is switched). The following table shows the
transform:
actual buffer | 'room' value before overflow calc
space avail | !I_PARMRK | I_PARMRK
--------------------------------------------------
0 | -1 | -1
1 | 0 | 0
2 | 1 | 0
3 | 2 | 0
4+ | 3 | 1
When !icanon or when icanon and the read buffer contains newlines,
normalized 'room' values of -1 and 0 are clamped to 0, and
'overflow' is 0, so read_head is not adjusted and the input i/o loop
exits (setting no_room if called from flush_to_ldisc()). No input
is discarded since the reader does have input available to read
which ensures forward progress.
When icanon and the read buffer does not contain newlines and the
normalized 'room' value is 0, then overflow and room are reset to 1,
so that the i/o loop will process the next input char normally
(except for parity errors which are ignored). Thus, erasures, signalling
chars, 7-bit mode, etc. will continue to be handled properly.
If the input char processed was not a line termination char, then
the canon_head index will not have advanced, so the normalized 'room'
value will now be -1 and 'overflow' will be set, which indicates the
read_head can safely be reset, effectively erasing the last char
processed.
If the input char processed was a line termination, then the
canon_head index will have advanced, so 'overflow' is cleared to 0,
the read_head is not reset, and 'room' is cleared to 0, which exits
the i/o loop (because the reader now have input available to read
which ensures forward progress).
Note that it is possible for a line termination to be received, and
for the reader to copy the line to the user buffer before the
input i/o loop is ready to process the next input char. This is
why the i/o loop recomputes the room/overflow state with every
input char while handling overflow.
Finally, if the input data was processed without receiving
a line termination (so that overflow is still set), the pty
driver must receive a write wakeup. A pty writer may be waiting
to write more data in n_tty_write() but without unthrottling
here that wakeup will not arrive, and forward progress will halt.
(Normally, the pty writer is woken when the reader reads data out
of the buffer and more space become available).
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-01-17 03:05:39 +07:00
|
|
|
|
|
|
|
/* Unthrottle if handling overflow on pty */
|
|
|
|
if (tty->driver->type == TTY_DRIVER_TYPE_PTY) {
|
|
|
|
if (overflow) {
|
|
|
|
tty_set_flow_change(tty, TTY_UNTHROTTLE_SAFE);
|
|
|
|
tty_unthrottle_safe(tty);
|
|
|
|
__tty_set_flow_change(tty, 0);
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
n_tty_check_throttle(tty);
|
|
|
|
|
2013-06-15 20:14:24 +07:00
|
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
|
2013-07-24 19:29:54 +07:00
|
|
|
return rcvd;
|
2013-06-15 20:14:15 +07:00
|
|
|
}
|
|
|
|
|
2013-12-03 02:24:41 +07:00
|
|
|
static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
n_tty_receive_buf_common(tty, cp, fp, count, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp,
|
|
|
|
char *fp, int count)
|
|
|
|
{
|
|
|
|
return n_tty_receive_buf_common(tty, cp, fp, count, 1);
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
int is_ignored(int sig)
|
|
|
|
{
|
|
|
|
return (sigismember(¤t->blocked, sig) ||
|
2008-02-08 19:18:44 +07:00
|
|
|
current->sighand->action[sig-1].sa.sa_handler == SIG_IGN);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_set_termios - termios data changed
|
|
|
|
* @tty: terminal
|
|
|
|
* @old: previous data
|
|
|
|
*
|
|
|
|
* Called by the tty layer when the user changes termios flags so
|
|
|
|
* that the line discipline can plan ahead. This function cannot sleep
|
2008-02-08 19:18:44 +07:00
|
|
|
* and is protected from re-entry by the tty layer. The user is
|
2005-04-17 05:20:36 +07:00
|
|
|
* guaranteed that this function will not be re-entered or in progress
|
|
|
|
* when the ldisc is closed.
|
2008-10-13 16:45:06 +07:00
|
|
|
*
|
2013-06-15 20:14:23 +07:00
|
|
|
* Locking: Caller holds tty->termios_rwsem
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
|
|
|
static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2008-10-13 16:44:17 +07:00
|
|
|
|
2013-09-17 23:53:35 +07:00
|
|
|
if (!old || (old->c_lflag ^ tty->termios.c_lflag) & ICANON) {
|
2012-10-19 03:26:40 +07:00
|
|
|
bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE);
|
2013-12-11 05:12:02 +07:00
|
|
|
ldata->line_start = ldata->read_tail;
|
|
|
|
if (!L_ICANON(tty) || !read_cnt(ldata)) {
|
|
|
|
ldata->canon_head = ldata->read_tail;
|
|
|
|
ldata->push = 0;
|
|
|
|
} else {
|
|
|
|
set_bit((ldata->read_head - 1) & (N_TTY_BUF_SIZE - 1),
|
|
|
|
ldata->read_flags);
|
|
|
|
ldata->canon_head = ldata->read_head;
|
|
|
|
ldata->push = 1;
|
|
|
|
}
|
2015-01-17 03:05:37 +07:00
|
|
|
ldata->commit_head = ldata->read_head;
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->erasing = 0;
|
2013-06-15 20:14:27 +07:00
|
|
|
ldata->lnext = 0;
|
2008-10-13 16:44:17 +07:00
|
|
|
}
|
|
|
|
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->icanon = (L_ICANON(tty) != 0);
|
2013-05-17 23:49:48 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
|
|
|
|
I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
|
|
|
|
I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
|
|
|
|
I_PARMRK(tty)) {
|
2013-06-15 21:21:20 +07:00
|
|
|
bitmap_zero(ldata->char_map, 256);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (I_IGNCR(tty) || I_ICRNL(tty))
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit('\r', ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (I_INLCR(tty))
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit('\n', ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (L_ICANON(tty)) {
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit(ERASE_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(KILL_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(EOF_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit('\n', ldata->char_map);
|
|
|
|
set_bit(EOL_CHAR(tty), ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_IEXTEN(tty)) {
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit(WERASE_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(LNEXT_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(EOL2_CHAR(tty), ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (L_ECHO(tty))
|
|
|
|
set_bit(REPRINT_CHAR(tty),
|
2013-06-15 21:21:20 +07:00
|
|
|
ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (I_IXON(tty)) {
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit(START_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(STOP_CHAR(tty), ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
if (L_ISIG(tty)) {
|
2013-06-15 21:21:20 +07:00
|
|
|
set_bit(INTR_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(QUIT_CHAR(tty), ldata->char_map);
|
|
|
|
set_bit(SUSP_CHAR(tty), ldata->char_map);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2013-06-15 21:21:20 +07:00
|
|
|
clear_bit(__DISABLED_CHAR, ldata->char_map);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->raw = 0;
|
|
|
|
ldata->real_raw = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->raw = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
|
|
|
|
(I_IGNPAR(tty) || !I_INPCK(tty)) &&
|
|
|
|
(tty->driver->flags & TTY_DRIVER_REAL_RAW))
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->real_raw = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
else
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->real_raw = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2013-05-09 13:16:47 +07:00
|
|
|
/*
|
|
|
|
* Fix tty hang when I_IXON(tty) is cleared, but the tty
|
|
|
|
* been stopped by STOP_CHAR(tty) before it.
|
|
|
|
*/
|
2014-02-12 04:34:55 +07:00
|
|
|
if (!I_IXON(tty) && old && (old->c_iflag & IXON) && !tty->flow_stopped) {
|
2013-05-09 13:16:47 +07:00
|
|
|
start_tty(tty);
|
2014-02-12 04:34:55 +07:00
|
|
|
process_echoes(tty);
|
|
|
|
}
|
2013-05-09 13:16:47 +07:00
|
|
|
|
2008-04-30 14:54:13 +07:00
|
|
|
/* The termios change make the tty ready for I/O */
|
tty: fix stall caused by missing memory barrier in drivers/tty/n_tty.c
My colleague ran into a program stall on a x86_64 server, where
n_tty_read() was waiting for data even if there was data in the buffer
in the pty. kernel stack for the stuck process looks like below.
#0 [ffff88303d107b58] __schedule at ffffffff815c4b20
#1 [ffff88303d107bd0] schedule at ffffffff815c513e
#2 [ffff88303d107bf0] schedule_timeout at ffffffff815c7818
#3 [ffff88303d107ca0] wait_woken at ffffffff81096bd2
#4 [ffff88303d107ce0] n_tty_read at ffffffff8136fa23
#5 [ffff88303d107dd0] tty_read at ffffffff81368013
#6 [ffff88303d107e20] __vfs_read at ffffffff811a3704
#7 [ffff88303d107ec0] vfs_read at ffffffff811a3a57
#8 [ffff88303d107f00] sys_read at ffffffff811a4306
#9 [ffff88303d107f50] entry_SYSCALL_64_fastpath at ffffffff815c86d7
There seems to be two problems causing this issue.
First, in drivers/tty/n_tty.c, __receive_buf() stores the data and
updates ldata->commit_head using smp_store_release() and then checks
the wait queue using waitqueue_active(). However, since there is no
memory barrier, __receive_buf() could return without calling
wake_up_interactive_poll(), and at the same time, n_tty_read() could
start to wait in wait_woken() as in the following chart.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
if (waitqueue_active(&tty->read_wait))
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
add_wait_queue(&tty->read_wait, &wait);
...
if (!input_available_p(tty, 0)) {
smp_store_release(&ldata->commit_head,
ldata->read_head);
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
The second problem is that n_tty_read() also lacks a memory barrier
call and could also cause __receive_buf() to return without calling
wake_up_interactive_poll(), and n_tty_read() to wait in wait_woken()
as in the chart below.
__receive_buf() n_tty_read()
------------------------------------------------------------------------
spin_lock_irqsave(&q->lock, flags);
/* from add_wait_queue() */
...
if (!input_available_p(tty, 0)) {
/* Memory operations issued after the
RELEASE may be completed before the
RELEASE operation has completed */
smp_store_release(&ldata->commit_head,
ldata->read_head);
if (waitqueue_active(&tty->read_wait))
__add_wait_queue(q, wait);
spin_unlock_irqrestore(&q->lock,flags);
/* from add_wait_queue() */
...
timeout = wait_woken(&wait,
TASK_INTERRUPTIBLE, timeout);
------------------------------------------------------------------------
There are also other places in drivers/tty/n_tty.c which have similar
calls to waitqueue_active(), so instead of adding many memory barrier
calls, this patch simply removes the call to waitqueue_active(),
leaving just wake_up*() behind.
This fixes both problems because, even though the memory access before
or after the spinlocks in both wake_up*() and add_wait_queue() can
sneak into the critical section, it cannot go past it and the critical
section assures that they will be serialized (please see "INTER-CPU
ACQUIRING BARRIER EFFECTS" in Documentation/memory-barriers.txt for a
better explanation). Moreover, the resulting code is much simpler.
Latency measurement using a ping-pong test over a pty doesn't show any
visible performance drop.
Signed-off-by: Kosuke Tatsukawa <tatsu@ab.jp.nec.com>
Cc: stable@vger.kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-10-02 15:27:05 +07:00
|
|
|
wake_up_interruptible(&tty->write_wait);
|
|
|
|
wake_up_interruptible(&tty->read_wait);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_close - close the ldisc for this tty
|
|
|
|
* @tty: device
|
|
|
|
*
|
2008-02-08 19:18:44 +07:00
|
|
|
* Called from the terminal layer when this line discipline is
|
|
|
|
* being shut down, either because of a close or becsuse of a
|
2005-04-17 05:20:36 +07:00
|
|
|
* discipline change. The function will not be called while other
|
|
|
|
* ldisc methods are in progress.
|
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static void n_tty_close(struct tty_struct *tty)
|
|
|
|
{
|
2012-10-19 03:26:38 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
n_tty: Don't flush buffer when closing ldisc
A buffer flush is both undesirable and unnecessary when the ldisc
is closing. A buffer flush performs the following:
1. resets ldisc data fields to their initial state
2. resets tty->receive_room to indicate more data can be sent
3. schedules buffer work to receive more data
4. signals a buffer flush has happened to linked pty in packet mode
Since the ldisc has been halted and the tty may soon be destructed,
buffer work must not be scheduled as that work might access
an invalid tty and ldisc state. Also, the ldisc read buffer is about
to be freed, so that's pointless.
Resetting the ldisc data fields is pointless as well since that
structure is about to be freed.
Resetting tty->receive_room is unnecessary, as it will be properly
reset if a new ldisc is reopened. Besides, resetting the original
receive_room value would be wrong since the read buffer will be
gone.
Since the packet mode flush is observable from userspace, this
behavior has been preserved.
The test jig originally authored by Ilya Zykov <ilya@ilyx.ru> and
signed off by him is included below. The test jig prompts the
following warnings which this patch fixes.
[ 38.051111] ------------[ cut here ]------------
[ 38.052113] WARNING: at drivers/tty/n_tty.c:160 n_tty_set_room.part.6+0x8b/0xa0()
[ 38.053916] Hardware name: Bochs
[ 38.054819] Modules linked in: netconsole configfs bnep rfcomm bluetooth parport_pc ppdev snd_hda_intel snd_hda_codec
snd_hwdep snd_pcm snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq psmouse snd_timer serio_raw mac_hid snd_seq_device
snd microcode lp parport virtio_balloon soundcore i2c_piix4 snd_page_alloc floppy 8139too 8139cp
[ 38.059704] Pid: 1564, comm: pty_kill Tainted: G W 3.7.0-next-20121130+ttydebug-xeon #20121130+ttydebug
[ 38.061578] Call Trace:
[ 38.062491] [<ffffffff81058b4f>] warn_slowpath_common+0x7f/0xc0
[ 38.063448] [<ffffffff81058baa>] warn_slowpath_null+0x1a/0x20
[ 38.064439] [<ffffffff8142dc2b>] n_tty_set_room.part.6+0x8b/0xa0
[ 38.065381] [<ffffffff8142dc82>] n_tty_set_room+0x42/0x80
[ 38.066323] [<ffffffff8142e6f2>] reset_buffer_flags+0x102/0x160
[ 38.077508] [<ffffffff8142e76d>] n_tty_flush_buffer+0x1d/0x90
[ 38.078782] [<ffffffff81046569>] ? default_spin_lock_flags+0x9/0x10
[ 38.079734] [<ffffffff8142e804>] n_tty_close+0x24/0x60
[ 38.080730] [<ffffffff81431b61>] tty_ldisc_close.isra.2+0x41/0x60
[ 38.081680] [<ffffffff81431bbb>] tty_ldisc_kill+0x3b/0x80
[ 38.082618] [<ffffffff81432a07>] tty_ldisc_release+0x77/0xe0
[ 38.083549] [<ffffffff8142b781>] tty_release+0x451/0x4d0
[ 38.084525] [<ffffffff811950be>] __fput+0xae/0x230
[ 38.085472] [<ffffffff8119524e>] ____fput+0xe/0x10
[ 38.086401] [<ffffffff8107aa88>] task_work_run+0xc8/0xf0
[ 38.087334] [<ffffffff8105ea56>] do_exit+0x196/0x4b0
[ 38.088304] [<ffffffff8106c77b>] ? __dequeue_signal+0x6b/0xb0
[ 38.089240] [<ffffffff8105ef34>] do_group_exit+0x44/0xa0
[ 38.090182] [<ffffffff8106f43d>] get_signal_to_deliver+0x20d/0x4e0
[ 38.091125] [<ffffffff81016979>] do_signal+0x29/0x130
[ 38.092096] [<ffffffff81431a9e>] ? tty_ldisc_deref+0xe/0x10
[ 38.093030] [<ffffffff8142a317>] ? tty_write+0xb7/0xf0
[ 38.093976] [<ffffffff81193f53>] ? vfs_write+0xb3/0x180
[ 38.094904] [<ffffffff81016b20>] do_notify_resume+0x80/0xc0
[ 38.095830] [<ffffffff81700492>] int_signal+0x12/0x17
[ 38.096788] ---[ end trace 5f6f7a9651cd999b ]---
[ 2730.570602] ------------[ cut here ]------------
[ 2730.572130] WARNING: at drivers/tty/n_tty.c:160 n_tty_set_room+0x107/0x140()
[ 2730.574904] scheduling buffer work for halted ldisc
[ 2730.578303] Pid: 9691, comm: trinity-child15 Tainted: G W 3.7.0-rc8-next-20121205-sasha-00023-g59f0d85 #207
[ 2730.588694] Call Trace:
[ 2730.590486] [<ffffffff81c41d77>] ? n_tty_set_room+0x107/0x140
[ 2730.592559] [<ffffffff8110c827>] warn_slowpath_common+0x87/0xb0
[ 2730.595317] [<ffffffff8110c8b1>] warn_slowpath_fmt+0x41/0x50
[ 2730.599186] [<ffffffff81c41d77>] n_tty_set_room+0x107/0x140
[ 2730.603141] [<ffffffff81c42fe7>] reset_buffer_flags+0x137/0x150
[ 2730.607166] [<ffffffff81c43018>] n_tty_flush_buffer+0x18/0x90
[ 2730.610123] [<ffffffff81c430af>] n_tty_close+0x1f/0x60
[ 2730.612068] [<ffffffff81c461f2>] tty_ldisc_close.isra.4+0x52/0x60
[ 2730.614078] [<ffffffff81c462ab>] tty_ldisc_reinit+0x3b/0x70
[ 2730.615891] [<ffffffff81c46db2>] tty_ldisc_hangup+0x102/0x1e0
[ 2730.617780] [<ffffffff81c3e537>] __tty_hangup+0x137/0x440
[ 2730.619547] [<ffffffff81c3e869>] tty_vhangup+0x9/0x10
[ 2730.621266] [<ffffffff81c48f1c>] pty_close+0x14c/0x160
[ 2730.622952] [<ffffffff81c3fd45>] tty_release+0xd5/0x490
[ 2730.624674] [<ffffffff8127fbe2>] __fput+0x122/0x250
[ 2730.626195] [<ffffffff8127fd19>] ____fput+0x9/0x10
[ 2730.627758] [<ffffffff81134602>] task_work_run+0xb2/0xf0
[ 2730.629491] [<ffffffff811139ad>] do_exit+0x36d/0x580
[ 2730.631159] [<ffffffff81113c8a>] do_group_exit+0x8a/0xc0
[ 2730.632819] [<ffffffff81127351>] get_signal_to_deliver+0x501/0x5b0
[ 2730.634758] [<ffffffff8106de34>] do_signal+0x24/0x100
[ 2730.636412] [<ffffffff81204865>] ? user_exit+0xa5/0xd0
[ 2730.638078] [<ffffffff81183cd8>] ? trace_hardirqs_on_caller+0x118/0x140
[ 2730.640279] [<ffffffff81183d0d>] ? trace_hardirqs_on+0xd/0x10
[ 2730.642164] [<ffffffff8106df78>] do_notify_resume+0x48/0xa0
[ 2730.643966] [<ffffffff83cdff6a>] int_signal+0x12/0x17
[ 2730.645672] ---[ end trace a40d53149c07fce0 ]---
/*
* pty_thrash.c
*
* Based on original test jig by Ilya Zykov <ilya@ilyx.ru>
*
* Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
* Signed-off-by: Ilya Zykov <ilya@ilyx.ru>
*/
static int fd;
static void error_exit(char *f, ...)
{
va_list va;
va_start(va, f);
vprintf(f, va);
printf(": %s\n", strerror(errno));
va_end(va);
if (fd >= 0)
close(fd);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
int parent;
char pts_name[24];
int ptn, unlock;
while (1) {
fd = open("/dev/ptmx", O_RDWR);
if (fd < 0)
error_exit("opening pty master");
unlock = 0;
if (ioctl(fd, TIOCSPTLCK, &unlock) < 0)
error_exit("unlocking pty pair");
if (ioctl(fd, TIOCGPTN, &ptn) < 0)
error_exit("getting pty #");
snprintf(pts_name, sizeof(pts_name), "/dev/pts/%d", ptn);
child_id = fork();
if (child_id == -1)
error_exit("forking child");
if (parent) {
int err, id, status;
char buf[128];
int n;
n = read(fd, buf, sizeof(buf));
if (n < 0)
error_exit("master reading");
printf("%.*s\n", n-1, buf);
close(fd);
err = kill(child_id, SIGKILL);
if (err < 0)
error_exit("killing child");
id = waitpid(child_id, &status, 0);
if (id < 0 || id != child_id)
error_exit("waiting for child");
} else { /* Child */
close(fd);
printf("Test cycle on slave pty %s\n", pts_name);
fd = open(pts_name, O_RDWR);
if (fd < 0)
error_exit("opening pty slave");
while (1) {
char pattern[] = "test\n";
if (write(fd, pattern, strlen(pattern)) < 0)
error_exit("slave writing");
}
}
}
/* never gets here */
return 0;
}
Reported-by: Sasha Levin <levinsasha928@gmail.com>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-03-12 03:44:23 +07:00
|
|
|
if (tty->link)
|
|
|
|
n_tty_packet_mode_flush(tty);
|
|
|
|
|
2013-06-15 21:21:19 +07:00
|
|
|
vfree(ldata);
|
2012-10-19 03:26:38 +07:00
|
|
|
tty->disc_data = NULL;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_open - open an ldisc
|
|
|
|
* @tty: terminal to open
|
|
|
|
*
|
2008-02-08 19:18:44 +07:00
|
|
|
* Called when this line discipline is being attached to the
|
2005-04-17 05:20:36 +07:00
|
|
|
* terminal device. Can sleep. Called serialized so that no
|
|
|
|
* other events will occur in parallel. No further open will occur
|
|
|
|
* until a close.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int n_tty_open(struct tty_struct *tty)
|
|
|
|
{
|
2012-10-19 03:26:38 +07:00
|
|
|
struct n_tty_data *ldata;
|
|
|
|
|
2013-06-15 21:21:19 +07:00
|
|
|
/* Currently a malloc failure here can panic */
|
|
|
|
ldata = vmalloc(sizeof(*ldata));
|
2012-10-19 03:26:38 +07:00
|
|
|
if (!ldata)
|
|
|
|
goto err;
|
|
|
|
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->overrun_time = jiffies;
|
2012-10-19 03:26:42 +07:00
|
|
|
mutex_init(&ldata->atomic_read_lock);
|
|
|
|
mutex_init(&ldata->output_lock);
|
2012-10-19 03:26:39 +07:00
|
|
|
|
2012-10-19 03:26:38 +07:00
|
|
|
tty->disc_data = ldata;
|
2013-03-12 03:44:32 +07:00
|
|
|
reset_buffer_flags(tty->disc_data);
|
2012-10-19 03:26:39 +07:00
|
|
|
ldata->column = 0;
|
2013-06-15 21:21:19 +07:00
|
|
|
ldata->canon_column = 0;
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = 1;
|
2013-06-15 21:21:19 +07:00
|
|
|
ldata->num_overrun = 0;
|
|
|
|
ldata->no_room = 0;
|
|
|
|
ldata->lnext = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
tty->closing = 0;
|
2013-03-12 03:44:32 +07:00
|
|
|
/* indicate buffer work may resume */
|
|
|
|
clear_bit(TTY_LDISC_HALTED, &tty->flags);
|
|
|
|
n_tty_set_termios(tty, NULL);
|
|
|
|
tty_unthrottle(tty);
|
2012-10-19 03:26:38 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
return 0;
|
2012-10-19 03:26:38 +07:00
|
|
|
err:
|
2012-10-19 03:26:35 +07:00
|
|
|
return -ENOMEM;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2013-12-03 02:24:45 +07:00
|
|
|
static inline int input_available_p(struct tty_struct *tty, int poll)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2014-02-11 23:49:58 +07:00
|
|
|
int amt = poll && !TIME_CHAR(tty) && MIN_CHAR(tty) ? MIN_CHAR(tty) : 1;
|
2012-10-19 03:26:39 +07:00
|
|
|
|
2014-02-12 06:55:30 +07:00
|
|
|
if (ldata->icanon && !L_EXTPROC(tty))
|
|
|
|
return ldata->canon_head != ldata->read_tail;
|
|
|
|
else
|
2015-01-17 03:05:37 +07:00
|
|
|
return ldata->commit_head - ldata->read_tail >= amt;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2015-04-14 00:24:34 +07:00
|
|
|
static inline int check_other_done(struct tty_struct *tty)
|
|
|
|
{
|
|
|
|
int done = test_bit(TTY_OTHER_DONE, &tty->flags);
|
|
|
|
if (done) {
|
|
|
|
/* paired with cmpxchg() in check_other_closed(); ensures
|
|
|
|
* read buffer head index is not stale
|
|
|
|
*/
|
|
|
|
smp_mb__after_atomic();
|
|
|
|
}
|
|
|
|
return done;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
/**
|
2011-12-08 23:47:33 +07:00
|
|
|
* copy_from_read_buf - copy read data directly
|
2005-04-17 05:20:36 +07:00
|
|
|
* @tty: terminal device
|
|
|
|
* @b: user data
|
|
|
|
* @nr: size of data
|
|
|
|
*
|
2008-10-13 16:46:24 +07:00
|
|
|
* Helper function to speed up n_tty_read. It is only called when
|
2005-04-17 05:20:36 +07:00
|
|
|
* ICANON is off; it copies characters straight from the tty queue to
|
|
|
|
* user space directly. It can be profitably called twice; once to
|
|
|
|
* drain the space from the tail pointer to the (physical) end of the
|
|
|
|
* buffer, and once to drain the space from the (physical) beginning of
|
|
|
|
* the buffer to head pointer.
|
|
|
|
*
|
2012-10-19 03:26:42 +07:00
|
|
|
* Called under the ldata->atomic_read_lock sem
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
2013-06-15 20:14:26 +07:00
|
|
|
* n_tty_read()/consumer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
|
|
|
* read_tail published
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
[PATCH] TTY layer buffering revamp
The API and code have been through various bits of initial review by
serial driver people but they definitely need to live somewhere for a
while so the unconverted drivers can get knocked into shape, existing
drivers that have been updated can be better tuned and bugs whacked out.
This replaces the tty flip buffers with kmalloc objects in rings. In the
normal situation for an IRQ driven serial port at typical speeds the
behaviour is pretty much the same, two buffers end up allocated and the
kernel cycles between them as before.
When there are delays or at high speed we now behave far better as the
buffer pool can grow a bit rather than lose characters. This also means
that we can operate at higher speeds reliably.
For drivers that receive characters in blocks (DMA based, USB and
especially virtualisation) the layer allows a lot of driver specific
code that works around the tty layer with private secondary queues to be
removed. The IBM folks need this sort of layer, the smart serial port
people do, the virtualisers do (because a virtualised tty typically
operates at infinite speed rather than emulating 9600 baud).
Finally many drivers had invalid and unsafe attempts to avoid buffer
overflows by directly invoking tty methods extracted out of the innards
of work queue structs. These are no longer needed and all go away. That
fixes various random hangs with serial ports on overflow.
The other change in here is to optimise the receive_room path that is
used by some callers. It turns out that only one ldisc uses receive room
except asa constant and it updates it far far less than the value is
read. We thus make it a variable not a function call.
I expect the code to contain bugs due to the size alone but I'll be
watching and squashing them and feeding out new patches as it goes.
Because the buffers now dynamically expand you should only run out of
buffering when the kernel runs out of memory for real. That means a lot of
the horrible hacks high performance drivers used to do just aren't needed any
more.
Description:
tty_insert_flip_char is an old API and continues to work as before, as does
tty_flip_buffer_push() [this is why many drivers dont need modification]. It
does now also return the number of chars inserted
There are also
tty_buffer_request_room(tty, len)
which asks for a buffer block of the length requested and returns the space
found. This improves efficiency with hardware that knows how much to
transfer.
and tty_insert_flip_string_flags(tty, str, flags, len)
to insert a string of characters and flags
For a smart interface the usual code is
len = tty_request_buffer_room(tty, amount_hardware_says);
tty_insert_flip_string(tty, buffer_from_card, len);
More description!
At the moment tty buffers are attached directly to the tty. This is causing a
lot of the problems related to tty layer locking, also problems at high speed
and also with bursty data (such as occurs in virtualised environments)
I'm working on ripping out the flip buffers and replacing them with a pool of
dynamically allocated buffers. This allows both for old style "byte I/O"
devices and also helps virtualisation and smart devices where large blocks of
data suddenely materialise and need storing.
So far so good. Lots of drivers reference tty->flip.*. Several of them also
call directly and unsafely into function pointers it provides. This will all
break. Most drivers can use tty_insert_flip_char which can be kept as an API
but others need more.
At the moment I've added the following interfaces, if people think more will
be needed now is a good time to say
int tty_buffer_request_room(tty, size)
Try and ensure at least size bytes are available, returns actual room (may be
zero). At the moment it just uses the flipbuf space but that will change.
Repeated calls without characters being added are not cumulative. (ie if you
call it with 1, 1, 1, and then 4 you'll have four characters of space. The
other functions will also try and grow buffers in future but this will be a
more efficient way when you know block sizes.
int tty_insert_flip_char(tty, ch, flag)
As before insert a character if there is room. Now returns 1 for success, 0
for failure.
int tty_insert_flip_string(tty, str, len)
Insert a block of non error characters. Returns the number inserted.
int tty_prepare_flip_string(tty, strptr, len)
Adjust the buffer to allow len characters to be added. Returns a buffer
pointer in strptr and the length available. This allows for hardware that
needs to use functions like insl or mencpy_fromio.
Signed-off-by: Alan Cox <alan@redhat.com>
Cc: Paul Fulghum <paulkf@microgate.com>
Signed-off-by: Hirokazu Takata <takata@linux-m32r.org>
Signed-off-by: Serge Hallyn <serue@us.ibm.com>
Signed-off-by: Jeff Dike <jdike@addtoit.com>
Signed-off-by: John Hawkes <hawkes@sgi.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 11:54:13 +07:00
|
|
|
static int copy_from_read_buf(struct tty_struct *tty,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned char __user **b,
|
|
|
|
size_t *nr)
|
|
|
|
|
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
int retval;
|
|
|
|
size_t n;
|
2012-04-27 01:13:00 +07:00
|
|
|
bool is_eof;
|
2015-01-17 03:05:37 +07:00
|
|
|
size_t head = smp_load_acquire(&ldata->commit_head);
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
retval = 0;
|
2015-01-17 03:05:37 +07:00
|
|
|
n = min(head - ldata->read_tail, N_TTY_BUF_SIZE - tail);
|
2005-04-17 05:20:36 +07:00
|
|
|
n = min(*nr, n);
|
|
|
|
if (n) {
|
2015-11-28 02:11:03 +07:00
|
|
|
const unsigned char *from = read_buf_addr(ldata, tail);
|
|
|
|
retval = copy_to_user(*b, from, n);
|
2005-04-17 05:20:36 +07:00
|
|
|
n -= retval;
|
2015-11-28 02:11:03 +07:00
|
|
|
is_eof = n == 1 && *from == EOF_CHAR(tty);
|
|
|
|
tty_audit_add_data(tty, from, n, ldata->icanon);
|
2015-01-17 03:05:37 +07:00
|
|
|
smp_store_release(&ldata->read_tail, ldata->read_tail + n);
|
tty: Add EXTPROC support for LINEMODE
This patch is against the 2.6.34 source.
Paraphrased from the 1989 BSD patch by David Borman @ cray.com:
These are the changes needed for the kernel to support
LINEMODE in the server.
There is a new bit in the termios local flag word, EXTPROC.
When this bit is set, several aspects of the terminal driver
are disabled. Input line editing, character echo, and mapping
of signals are all disabled. This allows the telnetd to turn
off these functions when in linemode, but still keep track of
what state the user wants the terminal to be in.
New ioctl:
TIOCSIG Generate a signal to processes in the
current process group of the pty.
There is a new mode for packet driver, the TIOCPKT_IOCTL bit.
When packet mode is turned on in the pty, and the EXTPROC bit
is set, then whenever the state of the pty is changed, the
next read on the master side of the pty will have the TIOCPKT_IOCTL
bit set. This allows the process on the server side of the pty
to know when the state of the terminal has changed; it can then
issue the appropriate ioctl to retrieve the new state.
Since the original BSD patches accompanied the source code for telnet
I've left that reference here, but obviously the feature is useful for
any remote terminal protocol, including ssh.
The corresponding feature has existed in the BSD tty driver since 1989.
For historical reference, a good copy of the relevant files can be found
here:
http://anonsvn.mit.edu/viewvc/krb5/trunk/src/appl/telnet/?pathrev=17741
Signed-off-by: Howard Chu <hyc@symas.com>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2010-06-23 00:14:49 +07:00
|
|
|
/* Turn single EOF into zero-length read */
|
2015-01-17 03:05:37 +07:00
|
|
|
if (L_EXTPROC(tty) && ldata->icanon && is_eof &&
|
|
|
|
(head == ldata->read_tail))
|
2012-04-27 01:13:00 +07:00
|
|
|
n = 0;
|
2005-04-17 05:20:36 +07:00
|
|
|
*b += n;
|
|
|
|
*nr -= n;
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:16 +07:00
|
|
|
/**
|
2013-06-15 20:14:17 +07:00
|
|
|
* canon_copy_from_read_buf - copy read data in canonical mode
|
2013-06-15 20:14:16 +07:00
|
|
|
* @tty: terminal device
|
|
|
|
* @b: user data
|
|
|
|
* @nr: size of data
|
|
|
|
*
|
|
|
|
* Helper function for n_tty_read. It is only called when ICANON is on;
|
2013-06-15 20:14:17 +07:00
|
|
|
* it copies one line of input up to and including the line-delimiting
|
|
|
|
* character into the user-space buffer.
|
2013-06-15 20:14:16 +07:00
|
|
|
*
|
2013-12-11 05:12:02 +07:00
|
|
|
* NB: When termios is changed from non-canonical to canonical mode and
|
|
|
|
* the read buffer contains data, n_tty_set_termios() simulates an EOF
|
|
|
|
* push (as if C-d were input) _without_ the DISABLED_CHAR in the buffer.
|
|
|
|
* This causes data already processed as input to be immediately available
|
|
|
|
* as input although a newline has not been received.
|
|
|
|
*
|
2013-06-15 20:14:16 +07:00
|
|
|
* Called under the atomic_read_lock mutex
|
2013-06-15 20:14:26 +07:00
|
|
|
*
|
|
|
|
* n_tty_read()/consumer path:
|
|
|
|
* caller holds non-exclusive termios_rwsem
|
|
|
|
* read_tail published
|
2013-06-15 20:14:16 +07:00
|
|
|
*/
|
|
|
|
|
2013-06-15 20:14:17 +07:00
|
|
|
static int canon_copy_from_read_buf(struct tty_struct *tty,
|
|
|
|
unsigned char __user **b,
|
|
|
|
size_t *nr)
|
2013-06-15 20:14:16 +07:00
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2013-06-15 20:14:17 +07:00
|
|
|
size_t n, size, more, c;
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t eol;
|
|
|
|
size_t tail;
|
|
|
|
int ret, found = 0;
|
2013-06-15 20:14:16 +07:00
|
|
|
|
|
|
|
/* N.B. avoid overrun if nr == 0 */
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
if (!*nr)
|
2013-06-15 20:14:17 +07:00
|
|
|
return 0;
|
2013-06-15 20:14:16 +07:00
|
|
|
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
n = min(*nr + 1, smp_load_acquire(&ldata->canon_head) - ldata->read_tail);
|
|
|
|
|
2013-06-15 20:14:21 +07:00
|
|
|
tail = ldata->read_tail & (N_TTY_BUF_SIZE - 1);
|
2013-06-15 20:14:17 +07:00
|
|
|
size = min_t(size_t, tail + n, N_TTY_BUF_SIZE);
|
|
|
|
|
2013-06-15 20:14:21 +07:00
|
|
|
n_tty_trace("%s: nr:%zu tail:%zu n:%zu size:%zu\n",
|
2013-06-15 20:14:17 +07:00
|
|
|
__func__, *nr, tail, n, size);
|
|
|
|
|
|
|
|
eol = find_next_bit(ldata->read_flags, size, tail);
|
|
|
|
more = n - (size - tail);
|
|
|
|
if (eol == N_TTY_BUF_SIZE && more) {
|
|
|
|
/* scan wrapped without finding set bit */
|
|
|
|
eol = find_next_bit(ldata->read_flags, more, 0);
|
2015-11-28 02:11:04 +07:00
|
|
|
found = eol != more;
|
|
|
|
} else
|
|
|
|
found = eol != size;
|
2013-06-15 20:14:17 +07:00
|
|
|
|
2013-11-22 19:16:25 +07:00
|
|
|
n = eol - tail;
|
2015-05-18 07:01:48 +07:00
|
|
|
if (n > N_TTY_BUF_SIZE)
|
|
|
|
n += N_TTY_BUF_SIZE;
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
c = n + found;
|
2013-06-15 20:14:17 +07:00
|
|
|
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
if (!found || read_buf(ldata, eol) != __DISABLED_CHAR) {
|
|
|
|
c = min(*nr, c);
|
|
|
|
n = c;
|
2013-06-15 21:21:17 +07:00
|
|
|
}
|
2013-06-15 20:14:17 +07:00
|
|
|
|
2015-11-28 02:11:02 +07:00
|
|
|
n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu tail:%zu more:%zu\n",
|
|
|
|
__func__, eol, found, n, c, tail, more);
|
2013-06-15 20:14:17 +07:00
|
|
|
|
2015-11-28 02:11:02 +07:00
|
|
|
ret = tty_copy_to_user(tty, *b, tail, n);
|
2013-06-15 20:14:17 +07:00
|
|
|
if (ret)
|
|
|
|
return -EFAULT;
|
|
|
|
*b += n;
|
|
|
|
*nr -= n;
|
|
|
|
|
2013-06-15 20:14:25 +07:00
|
|
|
if (found)
|
2013-06-15 20:14:26 +07:00
|
|
|
clear_bit(eol, ldata->read_flags);
|
2015-01-17 03:05:37 +07:00
|
|
|
smp_store_release(&ldata->read_tail, ldata->read_tail + c);
|
2013-06-15 20:14:16 +07:00
|
|
|
|
2013-06-15 21:21:17 +07:00
|
|
|
if (found) {
|
2013-12-11 05:12:02 +07:00
|
|
|
if (!ldata->push)
|
|
|
|
ldata->line_start = ldata->read_tail;
|
|
|
|
else
|
|
|
|
ldata->push = 0;
|
2013-06-15 20:14:17 +07:00
|
|
|
tty_audit_push(tty);
|
2013-06-15 21:21:17 +07:00
|
|
|
}
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
return 0;
|
2013-06-15 20:14:16 +07:00
|
|
|
}
|
|
|
|
|
2008-03-29 10:08:48 +07:00
|
|
|
extern ssize_t redirected_tty_write(struct file *, const char __user *,
|
2008-02-08 19:18:44 +07:00
|
|
|
size_t, loff_t *);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* job_control - check job control
|
|
|
|
* @tty: tty
|
|
|
|
* @file: file handle
|
|
|
|
*
|
|
|
|
* Perform job control management checks on this file/tty descriptor
|
2008-02-08 19:18:44 +07:00
|
|
|
* and if appropriate send any needed signals and return a negative
|
2005-04-17 05:20:36 +07:00
|
|
|
* error code if action should be taken.
|
2008-04-30 14:53:29 +07:00
|
|
|
*
|
2013-03-06 20:38:20 +07:00
|
|
|
* Locking: redirected write test is safe
|
|
|
|
* current->signal->tty check is safe
|
|
|
|
* ctrl_lock to safely reference tty->pgrp
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
static int job_control(struct tty_struct *tty, struct file *file)
|
|
|
|
{
|
|
|
|
/* Job control check -- must be done at start and after
|
|
|
|
every sleep (POSIX.1 7.1.1.4). */
|
|
|
|
/* NOTE: not yet done after every sleep pending a thorough
|
|
|
|
check of the logic of this change. -- jlc */
|
|
|
|
/* don't stop on /dev/console */
|
2015-10-11 07:28:42 +07:00
|
|
|
if (file->f_op->write == redirected_tty_write)
|
2013-03-06 20:38:20 +07:00
|
|
|
return 0;
|
|
|
|
|
2015-10-11 07:28:42 +07:00
|
|
|
return __tty_check_change(tty, SIGTTIN);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
/**
|
2008-10-13 16:46:24 +07:00
|
|
|
* n_tty_read - read function for tty
|
2005-04-17 05:20:36 +07:00
|
|
|
* @tty: tty device
|
|
|
|
* @file: file object
|
|
|
|
* @buf: userspace buffer pointer
|
|
|
|
* @nr: size of I/O
|
|
|
|
*
|
|
|
|
* Perform reads for the line discipline. We are guaranteed that the
|
|
|
|
* line discipline will not be closed under us but we may get multiple
|
|
|
|
* parallel readers and must handle this ourselves. We may also get
|
|
|
|
* a hangup. Always called in user context, may sleep.
|
|
|
|
*
|
|
|
|
* This code must be sure never to sleep through a hangup.
|
2013-06-15 20:14:26 +07:00
|
|
|
*
|
|
|
|
* n_tty_read()/consumer path:
|
|
|
|
* claims non-exclusive termios_rwsem
|
|
|
|
* publishes read_tail
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2008-10-13 16:46:24 +07:00
|
|
|
static ssize_t n_tty_read(struct tty_struct *tty, struct file *file,
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned char __user *buf, size_t nr)
|
|
|
|
{
|
2012-10-19 03:26:39 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned char __user *b = buf;
|
2014-09-24 15:18:51 +07:00
|
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
2015-04-14 00:24:34 +07:00
|
|
|
int c, done;
|
2005-04-17 05:20:36 +07:00
|
|
|
int minimum, time;
|
|
|
|
ssize_t retval = 0;
|
|
|
|
long timeout;
|
2008-04-30 14:53:29 +07:00
|
|
|
int packet;
|
2015-01-17 03:05:34 +07:00
|
|
|
size_t tail;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
c = job_control(tty, file);
|
2008-02-08 19:18:44 +07:00
|
|
|
if (c < 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
return c;
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2013-08-11 19:04:23 +07:00
|
|
|
/*
|
|
|
|
* Internal serialization of reads.
|
|
|
|
*/
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
if (!mutex_trylock(&ldata->atomic_read_lock))
|
|
|
|
return -EAGAIN;
|
|
|
|
} else {
|
|
|
|
if (mutex_lock_interruptible(&ldata->atomic_read_lock))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:24 +07:00
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
minimum = time = 0;
|
|
|
|
timeout = MAX_SCHEDULE_TIMEOUT;
|
2012-10-19 03:26:39 +07:00
|
|
|
if (!ldata->icanon) {
|
2005-04-17 05:20:36 +07:00
|
|
|
minimum = MIN_CHAR(tty);
|
|
|
|
if (minimum) {
|
2013-06-15 18:28:29 +07:00
|
|
|
time = (HZ / 10) * TIME_CHAR(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (time)
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
else if (!waitqueue_active(&tty->read_wait) ||
|
2013-06-15 18:28:28 +07:00
|
|
|
(ldata->minimum_to_wake > minimum))
|
|
|
|
ldata->minimum_to_wake = minimum;
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
2013-06-15 18:28:29 +07:00
|
|
|
timeout = (HZ / 10) * TIME_CHAR(tty);
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = minimum = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-30 14:53:29 +07:00
|
|
|
packet = tty->packet;
|
2015-01-17 03:05:34 +07:00
|
|
|
tail = ldata->read_tail;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
add_wait_queue(&tty->read_wait, &wait);
|
|
|
|
while (nr) {
|
|
|
|
/* First test for status change. */
|
2008-04-30 14:53:29 +07:00
|
|
|
if (packet && tty->link->ctrl_status) {
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned char cs;
|
|
|
|
if (b != buf)
|
|
|
|
break;
|
2014-10-17 02:33:25 +07:00
|
|
|
spin_lock_irq(&tty->link->ctrl_lock);
|
2005-04-17 05:20:36 +07:00
|
|
|
cs = tty->link->ctrl_status;
|
|
|
|
tty->link->ctrl_status = 0;
|
2014-10-17 02:33:25 +07:00
|
|
|
spin_unlock_irq(&tty->link->ctrl_lock);
|
Audit: add TTY input auditing
Add TTY input auditing, used to audit system administrator's actions. This is
required by various security standards such as DCID 6/3 and PCI to provide
non-repudiation of administrator's actions and to allow a review of past
actions if the administrator seems to overstep their duties or if the system
becomes misconfigured for unknown reasons. These requirements do not make it
necessary to audit TTY output as well.
Compared to an user-space keylogger, this approach records TTY input using the
audit subsystem, correlated with other audit events, and it is completely
transparent to the user-space application (e.g. the console ioctls still
work).
TTY input auditing works on a higher level than auditing all system calls
within the session, which would produce an overwhelming amount of mostly
useless audit events.
Add an "audit_tty" attribute, inherited across fork (). Data read from TTYs
by process with the attribute is sent to the audit subsystem by the kernel.
The audit netlink interface is extended to allow modifying the audit_tty
attribute, and to allow sending explanatory audit events from user-space (for
example, a shell might send an event containing the final command, after the
interactive command-line editing and history expansion is performed, which
might be difficult to decipher from the TTY input alone).
Because the "audit_tty" attribute is inherited across fork (), it would be set
e.g. for sshd restarted within an audited session. To prevent this, the
audit_tty attribute is cleared when a process with no open TTY file
descriptors (e.g. after daemon startup) opens a TTY.
See https://www.redhat.com/archives/linux-audit/2007-June/msg00000.html for a
more detailed rationale document for an older version of this patch.
[akpm@linux-foundation.org: build fix]
Signed-off-by: Miloslav Trmac <mitr@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: Paul Fulghum <paulkf@microgate.com>
Cc: Casey Schaufler <casey@schaufler-ca.com>
Cc: Steve Grubb <sgrubb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-07-16 13:40:56 +07:00
|
|
|
if (tty_put_user(tty, cs, b++)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
retval = -EFAULT;
|
|
|
|
b--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nr--;
|
|
|
|
break;
|
|
|
|
}
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2013-06-15 18:28:28 +07:00
|
|
|
if (((minimum - (b - buf)) < ldata->minimum_to_wake) &&
|
2005-04-17 05:20:36 +07:00
|
|
|
((minimum - (b - buf)) >= 1))
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = (minimum - (b - buf));
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2015-04-14 00:24:34 +07:00
|
|
|
done = check_other_done(tty);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
if (!input_available_p(tty, 0)) {
|
2015-04-14 00:24:34 +07:00
|
|
|
if (done) {
|
2014-11-06 00:13:05 +07:00
|
|
|
retval = -EIO;
|
|
|
|
break;
|
tty: Fix pty master read() after slave closes
Commit f95499c3030fe1bfad57745f2db1959c5b43dca8,
n_tty: Don't wait for buffer work in read() loop
creates a race window which can cause a pty master read()
to miss the last pty slave write(s) and return -EIO instead,
thus signalling the pty slave is closed. This can happen when
the pty slave is written and immediately closed but before the
tty buffer i/o loop receives the new input; the pty master
read() is scheduled, sees its read buffer is empty and the
pty slave has been closed, and exits.
Because tty_flush_to_ldisc() has significant performance impact
for parallel i/o, rather than revert the commit, special case this
condition (ie., when the read buffer is empty and the 'other' pty
has been closed) and, only then, wait for buffer work to complete
before re-testing if the read buffer is still empty.
As before, subsequent pty master reads return any available data
until no more data is available, and then returns -EIO to
indicate the pty slave has closed.
Reported-by: Mikael Pettersson <mikpelinux@gmail.com>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Tested-by: Mikael Pettersson <mikpelinux@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-09-28 00:27:05 +07:00
|
|
|
}
|
2014-11-06 00:13:05 +07:00
|
|
|
if (tty_hung_up_p(file))
|
|
|
|
break;
|
|
|
|
if (!timeout)
|
|
|
|
break;
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
retval = -EAGAIN;
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
}
|
2014-11-06 00:13:05 +07:00
|
|
|
if (signal_pending(current)) {
|
|
|
|
retval = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
up_read(&tty->termios_rwsem);
|
2013-06-15 20:14:24 +07:00
|
|
|
|
2014-12-15 06:23:32 +07:00
|
|
|
timeout = wait_woken(&wait, TASK_INTERRUPTIBLE,
|
|
|
|
timeout);
|
2013-06-15 20:14:24 +07:00
|
|
|
|
2014-11-06 00:13:05 +07:00
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
continue;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
|
2012-10-19 03:26:39 +07:00
|
|
|
if (ldata->icanon && !L_EXTPROC(tty)) {
|
2013-06-15 20:14:17 +07:00
|
|
|
retval = canon_copy_from_read_buf(tty, &b, &nr);
|
n_tty: Fix poll() after buffer-limited eof push read
commit 40d5e0905a03 ("n_tty: Fix EOF push handling") fixed EOF push
for reads. However, that approach still allows a condition mismatch
between poll() and read(), where poll() returns POLLIN but read()
blocks. This state can happen when a previous read() returned because
the user buffer was full and the next character was an EOF not at the
beginning of the line. While the next read() will properly identify
the condition and advance the read buffer tail without improperly
indicating an EOF file condition (ie., read() will not mistakenly
return 0), poll() will mistakenly indicate POLLIN.
Although a possible solution would be to peek at the input buffer
in n_tty_poll(), the better solution in this patch is to eat the
EOF during the previous read() (ie., fix the problem by eliminating
the condition).
The current canon line buffer copy limits the scan for next end-of-line
to the smaller of either,
a. the remaining user buffer size
b. completed lines in the input buffer
When the remaining user buffer size is exactly one less than the
end-of-line marked by EOF push, the EOF is not scanned nor skipped
but left for subsequent reads. In the example below, the scan
index 'eol' has stopped at the EOF because it is past the scan
limit of 5 (not because it has found the next set bit in read_flags)
user buffer [*nr = 5] _ _ _ _ _
read_flags 0 0 0 0 0 1
input buffer h e l l o [EOF]
^ ^
/ /
tail eol
result: found = 0, tail += 5, *nr += 5
Instead, allow the scan to peek ahead 1 byte (while still limiting the
scan to completed lines in the input buffer). For the example above,
result: found = 1, tail += 6, *nr += 5
Because the scan limit is now bumped +1 byte, when the scan is
completed, the tail advance and the user buffer copy limit is
re-clamped to *nr when EOF is _not_ found.
Fixes: 40d5e0905a03 ("n_tty: Fix EOF push handling")
Cc: <stable@vger.kernel.org> # 3.12+
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-28 01:59:20 +07:00
|
|
|
if (retval)
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
int uncopied;
|
2014-10-17 02:33:30 +07:00
|
|
|
|
|
|
|
/* Deal with packet mode. */
|
|
|
|
if (packet && b == buf) {
|
|
|
|
if (tty_put_user(tty, TIOCPKT_DATA, b++)) {
|
|
|
|
retval = -EFAULT;
|
|
|
|
b--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nr--;
|
|
|
|
}
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
uncopied = copy_from_read_buf(tty, &b, &nr);
|
|
|
|
uncopied += copy_from_read_buf(tty, &b, &nr);
|
|
|
|
if (uncopied) {
|
|
|
|
retval = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:33 +07:00
|
|
|
n_tty_check_unthrottle(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
if (b - buf >= minimum)
|
|
|
|
break;
|
|
|
|
if (time)
|
|
|
|
timeout = time;
|
|
|
|
}
|
2015-01-17 03:05:34 +07:00
|
|
|
if (tail != ldata->read_tail)
|
|
|
|
n_tty_kick_worker(tty);
|
2013-11-08 01:59:46 +07:00
|
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
remove_wait_queue(&tty->read_wait, &wait);
|
|
|
|
if (!waitqueue_active(&tty->read_wait))
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = minimum;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
2013-11-08 02:01:57 +07:00
|
|
|
mutex_unlock(&ldata->atomic_read_lock);
|
|
|
|
|
2013-06-15 21:21:17 +07:00
|
|
|
if (b - buf)
|
|
|
|
retval = b - buf;
|
2005-04-17 05:20:36 +07:00
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-13 16:46:24 +07:00
|
|
|
* n_tty_write - write function for tty
|
2005-04-17 05:20:36 +07:00
|
|
|
* @tty: tty device
|
|
|
|
* @file: file object
|
|
|
|
* @buf: userspace buffer pointer
|
|
|
|
* @nr: size of I/O
|
|
|
|
*
|
2009-01-02 20:40:53 +07:00
|
|
|
* Write function of the terminal device. This is serialized with
|
2005-04-17 05:20:36 +07:00
|
|
|
* respect to other write callers but not to termios changes, reads
|
2009-01-02 20:40:53 +07:00
|
|
|
* and other such events. Since the receive code will echo characters,
|
|
|
|
* thus calling driver write methods, the output_lock is used in
|
|
|
|
* the output processing functions called here as well as in the
|
|
|
|
* echo processing function to protect the column state and space
|
|
|
|
* left in the buffer.
|
2005-04-17 05:20:36 +07:00
|
|
|
*
|
|
|
|
* This code must be sure never to sleep through a hangup.
|
2009-01-02 20:40:53 +07:00
|
|
|
*
|
|
|
|
* Locking: output_lock to protect column state and space left
|
|
|
|
* (note that the process_output*() functions take this
|
|
|
|
* lock themselves)
|
2005-04-17 05:20:36 +07:00
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2008-10-13 16:46:24 +07:00
|
|
|
static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
|
2009-01-02 20:40:53 +07:00
|
|
|
const unsigned char *buf, size_t nr)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
|
|
|
const unsigned char *b = buf;
|
2014-09-24 15:18:51 +07:00
|
|
|
DEFINE_WAIT_FUNC(wait, woken_wake_function);
|
2005-04-17 05:20:36 +07:00
|
|
|
int c;
|
|
|
|
ssize_t retval = 0;
|
|
|
|
|
|
|
|
/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
|
|
|
|
if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
|
|
|
|
retval = tty_check_change(tty);
|
|
|
|
if (retval)
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2013-06-15 20:14:24 +07:00
|
|
|
down_read(&tty->termios_rwsem);
|
|
|
|
|
2009-01-02 20:40:53 +07:00
|
|
|
/* Write out any echoed characters that are still pending */
|
|
|
|
process_echoes(tty);
|
2009-01-02 20:41:04 +07:00
|
|
|
|
2005-04-17 05:20:36 +07:00
|
|
|
add_wait_queue(&tty->write_wait, &wait);
|
|
|
|
while (1) {
|
|
|
|
if (signal_pending(current)) {
|
|
|
|
retval = -ERESTARTSYS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
|
|
|
|
retval = -EIO;
|
|
|
|
break;
|
|
|
|
}
|
2013-05-17 23:49:48 +07:00
|
|
|
if (O_OPOST(tty)) {
|
2005-04-17 05:20:36 +07:00
|
|
|
while (nr > 0) {
|
2009-01-02 20:40:53 +07:00
|
|
|
ssize_t num = process_output_block(tty, b, nr);
|
2005-04-17 05:20:36 +07:00
|
|
|
if (num < 0) {
|
|
|
|
if (num == -EAGAIN)
|
|
|
|
break;
|
|
|
|
retval = num;
|
|
|
|
goto break_out;
|
|
|
|
}
|
|
|
|
b += num;
|
|
|
|
nr -= num;
|
|
|
|
if (nr == 0)
|
|
|
|
break;
|
|
|
|
c = *b;
|
2009-01-02 20:40:53 +07:00
|
|
|
if (process_output(c, tty) < 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
break;
|
|
|
|
b++; nr--;
|
|
|
|
}
|
2008-04-30 14:54:13 +07:00
|
|
|
if (tty->ops->flush_chars)
|
|
|
|
tty->ops->flush_chars(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
} else {
|
n_tty: Fix n_tty_write crash when echoing in raw mode
The tty atomic_write_lock does not provide an exclusion guarantee for
the tty driver if the termios settings are LECHO & !OPOST. And since
it is unexpected and not allowed to call TTY buffer helpers like
tty_insert_flip_string concurrently, this may lead to crashes when
concurrect writers call pty_write. In that case the following two
writers:
* the ECHOing from a workqueue and
* pty_write from the process
race and can overflow the corresponding TTY buffer like follows.
If we look into tty_insert_flip_string_fixed_flag, there is:
int space = __tty_buffer_request_room(port, goal, flags);
struct tty_buffer *tb = port->buf.tail;
...
memcpy(char_buf_ptr(tb, tb->used), chars, space);
...
tb->used += space;
so the race of the two can result in something like this:
A B
__tty_buffer_request_room
__tty_buffer_request_room
memcpy(buf(tb->used), ...)
tb->used += space;
memcpy(buf(tb->used), ...) ->BOOM
B's memcpy is past the tty_buffer due to the previous A's tb->used
increment.
Since the N_TTY line discipline input processing can output
concurrently with a tty write, obtain the N_TTY ldisc output_lock to
serialize echo output with normal tty writes. This ensures the tty
buffer helper tty_insert_flip_string is not called concurrently and
everything is fine.
Note that this is nicely reproducible by an ordinary user using
forkpty and some setup around that (raw termios + ECHO). And it is
present in kernels at least after commit
d945cb9cce20ac7143c2de8d88b187f62db99bdc (pty: Rework the pty layer to
use the normal buffering logic) in 2.6.31-rc3.
js: add more info to the commit log
js: switch to bool
js: lock unconditionally
js: lock only the tty->ops->write call
References: CVE-2014-0196
Reported-and-tested-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2014-05-03 19:04:59 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
2005-07-08 07:56:55 +07:00
|
|
|
while (nr > 0) {
|
n_tty: Fix n_tty_write crash when echoing in raw mode
The tty atomic_write_lock does not provide an exclusion guarantee for
the tty driver if the termios settings are LECHO & !OPOST. And since
it is unexpected and not allowed to call TTY buffer helpers like
tty_insert_flip_string concurrently, this may lead to crashes when
concurrect writers call pty_write. In that case the following two
writers:
* the ECHOing from a workqueue and
* pty_write from the process
race and can overflow the corresponding TTY buffer like follows.
If we look into tty_insert_flip_string_fixed_flag, there is:
int space = __tty_buffer_request_room(port, goal, flags);
struct tty_buffer *tb = port->buf.tail;
...
memcpy(char_buf_ptr(tb, tb->used), chars, space);
...
tb->used += space;
so the race of the two can result in something like this:
A B
__tty_buffer_request_room
__tty_buffer_request_room
memcpy(buf(tb->used), ...)
tb->used += space;
memcpy(buf(tb->used), ...) ->BOOM
B's memcpy is past the tty_buffer due to the previous A's tb->used
increment.
Since the N_TTY line discipline input processing can output
concurrently with a tty write, obtain the N_TTY ldisc output_lock to
serialize echo output with normal tty writes. This ensures the tty
buffer helper tty_insert_flip_string is not called concurrently and
everything is fine.
Note that this is nicely reproducible by an ordinary user using
forkpty and some setup around that (raw termios + ECHO). And it is
present in kernels at least after commit
d945cb9cce20ac7143c2de8d88b187f62db99bdc (pty: Rework the pty layer to
use the normal buffering logic) in 2.6.31-rc3.
js: add more info to the commit log
js: switch to bool
js: lock unconditionally
js: lock only the tty->ops->write call
References: CVE-2014-0196
Reported-and-tested-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2014-05-03 19:04:59 +07:00
|
|
|
mutex_lock(&ldata->output_lock);
|
2008-04-30 14:54:13 +07:00
|
|
|
c = tty->ops->write(tty, b, nr);
|
n_tty: Fix n_tty_write crash when echoing in raw mode
The tty atomic_write_lock does not provide an exclusion guarantee for
the tty driver if the termios settings are LECHO & !OPOST. And since
it is unexpected and not allowed to call TTY buffer helpers like
tty_insert_flip_string concurrently, this may lead to crashes when
concurrect writers call pty_write. In that case the following two
writers:
* the ECHOing from a workqueue and
* pty_write from the process
race and can overflow the corresponding TTY buffer like follows.
If we look into tty_insert_flip_string_fixed_flag, there is:
int space = __tty_buffer_request_room(port, goal, flags);
struct tty_buffer *tb = port->buf.tail;
...
memcpy(char_buf_ptr(tb, tb->used), chars, space);
...
tb->used += space;
so the race of the two can result in something like this:
A B
__tty_buffer_request_room
__tty_buffer_request_room
memcpy(buf(tb->used), ...)
tb->used += space;
memcpy(buf(tb->used), ...) ->BOOM
B's memcpy is past the tty_buffer due to the previous A's tb->used
increment.
Since the N_TTY line discipline input processing can output
concurrently with a tty write, obtain the N_TTY ldisc output_lock to
serialize echo output with normal tty writes. This ensures the tty
buffer helper tty_insert_flip_string is not called concurrently and
everything is fine.
Note that this is nicely reproducible by an ordinary user using
forkpty and some setup around that (raw termios + ECHO). And it is
present in kernels at least after commit
d945cb9cce20ac7143c2de8d88b187f62db99bdc (pty: Rework the pty layer to
use the normal buffering logic) in 2.6.31-rc3.
js: add more info to the commit log
js: switch to bool
js: lock unconditionally
js: lock only the tty->ops->write call
References: CVE-2014-0196
Reported-and-tested-by: Jiri Slaby <jslaby@suse.cz>
Signed-off-by: Peter Hurley <peter@hurleysoftware.com>
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Cc: <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2014-05-03 19:04:59 +07:00
|
|
|
mutex_unlock(&ldata->output_lock);
|
2005-07-08 07:56:55 +07:00
|
|
|
if (c < 0) {
|
|
|
|
retval = c;
|
|
|
|
goto break_out;
|
|
|
|
}
|
|
|
|
if (!c)
|
|
|
|
break;
|
|
|
|
b += c;
|
|
|
|
nr -= c;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!nr)
|
|
|
|
break;
|
|
|
|
if (file->f_flags & O_NONBLOCK) {
|
|
|
|
retval = -EAGAIN;
|
|
|
|
break;
|
|
|
|
}
|
2013-06-15 20:14:24 +07:00
|
|
|
up_read(&tty->termios_rwsem);
|
|
|
|
|
2014-09-24 15:18:51 +07:00
|
|
|
wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
|
2013-06-15 20:14:24 +07:00
|
|
|
|
|
|
|
down_read(&tty->termios_rwsem);
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
|
|
|
break_out:
|
|
|
|
remove_wait_queue(&tty->write_wait, &wait);
|
2009-01-02 20:47:13 +07:00
|
|
|
if (b - buf != nr && tty->fasync)
|
|
|
|
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
|
2013-06-15 20:14:24 +07:00
|
|
|
up_read(&tty->termios_rwsem);
|
2005-04-17 05:20:36 +07:00
|
|
|
return (b - buf) ? b - buf : retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2008-10-13 16:46:24 +07:00
|
|
|
* n_tty_poll - poll method for N_TTY
|
2005-04-17 05:20:36 +07:00
|
|
|
* @tty: terminal device
|
|
|
|
* @file: file accessing it
|
|
|
|
* @wait: poll table
|
|
|
|
*
|
|
|
|
* Called when the line discipline is asked to poll() for data or
|
|
|
|
* for special events. This code is not serialized with respect to
|
|
|
|
* other events save open/close.
|
|
|
|
*
|
|
|
|
* This code must be sure never to sleep through a hangup.
|
|
|
|
* Called without the kernel lock held - fine
|
|
|
|
*/
|
2008-02-08 19:18:44 +07:00
|
|
|
|
2008-10-13 16:46:24 +07:00
|
|
|
static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,
|
2008-02-08 19:18:44 +07:00
|
|
|
poll_table *wait)
|
2005-04-17 05:20:36 +07:00
|
|
|
{
|
2013-06-15 18:28:28 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2005-04-17 05:20:36 +07:00
|
|
|
unsigned int mask = 0;
|
|
|
|
|
|
|
|
poll_wait(file, &tty->read_wait, wait);
|
|
|
|
poll_wait(file, &tty->write_wait, wait);
|
2015-04-14 00:24:34 +07:00
|
|
|
if (check_other_done(tty))
|
|
|
|
mask |= POLLHUP;
|
2013-12-03 02:24:45 +07:00
|
|
|
if (input_available_p(tty, 1))
|
2005-04-17 05:20:36 +07:00
|
|
|
mask |= POLLIN | POLLRDNORM;
|
|
|
|
if (tty->packet && tty->link->ctrl_status)
|
|
|
|
mask |= POLLPRI | POLLIN | POLLRDNORM;
|
|
|
|
if (tty_hung_up_p(file))
|
|
|
|
mask |= POLLHUP;
|
|
|
|
if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {
|
|
|
|
if (MIN_CHAR(tty) && !TIME_CHAR(tty))
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = MIN_CHAR(tty);
|
2005-04-17 05:20:36 +07:00
|
|
|
else
|
2013-06-15 18:28:28 +07:00
|
|
|
ldata->minimum_to_wake = 1;
|
2005-04-17 05:20:36 +07:00
|
|
|
}
|
2008-04-30 14:54:13 +07:00
|
|
|
if (tty->ops->write && !tty_is_writelocked(tty) &&
|
|
|
|
tty_chars_in_buffer(tty) < WAKEUP_CHARS &&
|
|
|
|
tty_write_room(tty) > 0)
|
2005-04-17 05:20:36 +07:00
|
|
|
mask |= POLLOUT | POLLWRNORM;
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
2012-10-19 03:26:43 +07:00
|
|
|
static unsigned long inq_canon(struct n_tty_data *ldata)
|
2008-10-13 16:44:17 +07:00
|
|
|
{
|
2013-06-15 20:14:21 +07:00
|
|
|
size_t nr, head, tail;
|
2008-10-13 16:44:17 +07:00
|
|
|
|
2013-06-15 20:14:25 +07:00
|
|
|
if (ldata->canon_head == ldata->read_tail)
|
2008-10-13 16:44:17 +07:00
|
|
|
return 0;
|
2012-10-19 03:26:41 +07:00
|
|
|
head = ldata->canon_head;
|
|
|
|
tail = ldata->read_tail;
|
2013-06-15 20:14:21 +07:00
|
|
|
nr = head - tail;
|
2008-10-13 16:44:17 +07:00
|
|
|
/* Skip EOF-chars.. */
|
|
|
|
while (head != tail) {
|
2013-06-15 20:14:21 +07:00
|
|
|
if (test_bit(tail & (N_TTY_BUF_SIZE - 1), ldata->read_flags) &&
|
|
|
|
read_buf(ldata, tail) == __DISABLED_CHAR)
|
2008-10-13 16:44:17 +07:00
|
|
|
nr--;
|
2013-06-15 20:14:21 +07:00
|
|
|
tail++;
|
2008-10-13 16:44:17 +07:00
|
|
|
}
|
|
|
|
return nr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int n_tty_ioctl(struct tty_struct *tty, struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
2012-10-19 03:26:41 +07:00
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
2008-10-13 16:44:17 +07:00
|
|
|
int retval;
|
|
|
|
|
|
|
|
switch (cmd) {
|
|
|
|
case TIOCOUTQ:
|
|
|
|
return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
|
|
|
|
case TIOCINQ:
|
2013-06-15 20:14:26 +07:00
|
|
|
down_write(&tty->termios_rwsem);
|
2008-10-13 16:44:17 +07:00
|
|
|
if (L_ICANON(tty))
|
2012-10-19 03:26:43 +07:00
|
|
|
retval = inq_canon(ldata);
|
2013-06-15 20:14:26 +07:00
|
|
|
else
|
|
|
|
retval = read_cnt(ldata);
|
|
|
|
up_write(&tty->termios_rwsem);
|
2008-10-13 16:44:17 +07:00
|
|
|
return put_user(retval, (unsigned int __user *) arg);
|
|
|
|
default:
|
|
|
|
return n_tty_ioctl_helper(tty, file, cmd, arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-15 18:28:28 +07:00
|
|
|
static void n_tty_fasync(struct tty_struct *tty, int on)
|
|
|
|
{
|
|
|
|
struct n_tty_data *ldata = tty->disc_data;
|
|
|
|
|
|
|
|
if (!waitqueue_active(&tty->read_wait)) {
|
|
|
|
if (on)
|
|
|
|
ldata->minimum_to_wake = 1;
|
|
|
|
else if (!tty->fasync)
|
|
|
|
ldata->minimum_to_wake = N_TTY_BUF_SIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-07-17 03:53:12 +07:00
|
|
|
struct tty_ldisc_ops tty_ldisc_N_TTY = {
|
2007-05-11 12:22:50 +07:00
|
|
|
.magic = TTY_LDISC_MAGIC,
|
|
|
|
.name = "n_tty",
|
|
|
|
.open = n_tty_open,
|
|
|
|
.close = n_tty_close,
|
|
|
|
.flush_buffer = n_tty_flush_buffer,
|
|
|
|
.chars_in_buffer = n_tty_chars_in_buffer,
|
2008-10-13 16:46:24 +07:00
|
|
|
.read = n_tty_read,
|
|
|
|
.write = n_tty_write,
|
2007-05-11 12:22:50 +07:00
|
|
|
.ioctl = n_tty_ioctl,
|
|
|
|
.set_termios = n_tty_set_termios,
|
2008-10-13 16:46:24 +07:00
|
|
|
.poll = n_tty_poll,
|
2007-05-11 12:22:50 +07:00
|
|
|
.receive_buf = n_tty_receive_buf,
|
2013-06-15 18:28:28 +07:00
|
|
|
.write_wakeup = n_tty_write_wakeup,
|
|
|
|
.fasync = n_tty_fasync,
|
2013-06-15 20:14:15 +07:00
|
|
|
.receive_buf2 = n_tty_receive_buf2,
|
2005-04-17 05:20:36 +07:00
|
|
|
};
|
2010-03-11 06:23:46 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* n_tty_inherit_ops - inherit N_TTY methods
|
|
|
|
* @ops: struct tty_ldisc_ops where to save N_TTY methods
|
|
|
|
*
|
2013-02-12 14:00:43 +07:00
|
|
|
* Enables a 'subclass' line discipline to 'inherit' N_TTY
|
2010-03-11 06:23:46 +07:00
|
|
|
* methods.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void n_tty_inherit_ops(struct tty_ldisc_ops *ops)
|
|
|
|
{
|
|
|
|
*ops = tty_ldisc_N_TTY;
|
|
|
|
ops->owner = NULL;
|
|
|
|
ops->refcount = ops->flags = 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(n_tty_inherit_ops);
|