gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/devpts/line_discipline.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package devpts
    16  
    17  import (
    18  	"bytes"
    19  	"unicode"
    20  	"unicode/utf8"
    21  
    22  	"gvisor.dev/gvisor/pkg/abi/linux"
    23  	"gvisor.dev/gvisor/pkg/context"
    24  	"gvisor.dev/gvisor/pkg/errors/linuxerr"
    25  	"gvisor.dev/gvisor/pkg/sentry/arch"
    26  	"gvisor.dev/gvisor/pkg/sentry/kernel"
    27  	"gvisor.dev/gvisor/pkg/sync"
    28  	"gvisor.dev/gvisor/pkg/usermem"
    29  	"gvisor.dev/gvisor/pkg/waiter"
    30  )
    31  
    32  const (
    33  	// canonMaxBytes is the number of bytes that fit into a single line of
    34  	// terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE
    35  	// in include/linux/tty.h.
    36  	canonMaxBytes = 4096
    37  
    38  	// nonCanonMaxBytes is the maximum number of bytes that can be read at
    39  	// a time in noncanonical mode.
    40  	nonCanonMaxBytes = canonMaxBytes - 1
    41  
    42  	spacesPerTab = 8
    43  )
    44  
    45  // lineDiscipline dictates how input and output are handled between the
    46  // pseudoterminal (pty) master and replica. It can be configured to alter I/O,
    47  // modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man
    48  // pages are good resources for how to affect the line discipline:
    49  //
    50  //   - termios(3)
    51  //   - tty_ioctl(4)
    52  //
    53  // This file corresponds most closely to drivers/tty/n_tty.c.
    54  //
    55  // lineDiscipline has a simple structure but supports a multitude of options
    56  // (see the above man pages). It consists of two queues of bytes: one from the
    57  // terminal master to replica (the input queue) and one from replica to master
    58  // (the output queue). When bytes are written to one end of the pty, the line
    59  // discipline reads the bytes, modifies them or takes special action if
    60  // required, and enqueues them to be read by the other end of the pty:
    61  //
    62  //	   input from terminal    +-------------+   input to process (e.g. bash)
    63  //	+------------------------>| input queue |---------------------------+
    64  //	|   (inputQueueWrite)     +-------------+     (inputQueueRead)      |
    65  //	|                                                                   |
    66  //	|                                                                   v
    67  //
    68  // masterFD                                                           replicaFD
    69  //
    70  //	^                                                                   |
    71  //	|                                                                   |
    72  //	|   output to terminal   +--------------+    output from process    |
    73  //	+------------------------| output queue |<--------------------------+
    74  //	    (outputQueueRead)    +--------------+    (outputQueueWrite)
    75  //
    76  // There is special handling for the ECHO option, where bytes written to the
    77  // input queue are also output back to the terminal by being written to
    78  // l.outQueue by the input queue transformer.
    79  //
    80  // Lock order:
    81  //
    82  //	termiosMu
    83  //	  inQueue.mu
    84  //	    outQueue.mu
    85  //
    86  // +stateify savable
    87  type lineDiscipline struct {
    88  	// sizeMu protects size.
    89  	sizeMu sync.Mutex `state:"nosave"`
    90  
    91  	// size is the terminal size (width and height).
    92  	size linux.WindowSize
    93  
    94  	// inQueue is the input queue of the terminal.
    95  	inQueue queue
    96  
    97  	// outQueue is the output queue of the terminal.
    98  	outQueue queue
    99  
   100  	// termiosMu protects termios.
   101  	termiosMu sync.RWMutex `state:"nosave"`
   102  
   103  	// termios is the terminal configuration used by the lineDiscipline.
   104  	termios linux.KernelTermios
   105  
   106  	// column is the location in a row of the cursor. This is important for
   107  	// handling certain special characters like backspace.
   108  	column int
   109  
   110  	// numReplicas is the number of replica file descriptors.
   111  	numReplicas int
   112  
   113  	// masterWaiter is used to wait on the master end of the TTY.
   114  	masterWaiter waiter.Queue
   115  
   116  	// replicaWaiter is used to wait on the replica end of the TTY.
   117  	replicaWaiter waiter.Queue
   118  
   119  	// terminal is the terminal linked to this lineDiscipline.
   120  	terminal *Terminal
   121  }
   122  
   123  func newLineDiscipline(termios linux.KernelTermios, terminal *Terminal) *lineDiscipline {
   124  	ld := lineDiscipline{
   125  		termios:  termios,
   126  		terminal: terminal,
   127  	}
   128  	ld.inQueue.transformer = &inputQueueTransformer{}
   129  	ld.outQueue.transformer = &outputQueueTransformer{}
   130  	return &ld
   131  }
   132  
   133  // getTermios gets the linux.Termios for the tty.
   134  func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   135  	l.termiosMu.RLock()
   136  	defer l.termiosMu.RUnlock()
   137  	// We must copy a Termios struct, not KernelTermios.
   138  	t := l.termios.ToTermios()
   139  	_, err := t.CopyOut(task, args[2].Pointer())
   140  	return 0, err
   141  }
   142  
   143  // setTermios sets a linux.Termios for the tty.
   144  func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   145  	l.termiosMu.Lock()
   146  	oldCanonEnabled := l.termios.LEnabled(linux.ICANON)
   147  	// We must copy a Termios struct, not KernelTermios.
   148  	var t linux.Termios
   149  	_, err := t.CopyIn(task, args[2].Pointer())
   150  	l.termios.FromTermios(t)
   151  
   152  	// If canonical mode is turned off, move bytes from inQueue's wait
   153  	// buffer to its read buffer. Anything already in the read buffer is
   154  	// now readable.
   155  	if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) {
   156  		l.inQueue.mu.Lock()
   157  		l.inQueue.pushWaitBufLocked(l)
   158  		l.inQueue.readable = len(l.inQueue.readBuf) > 0
   159  		l.inQueue.mu.Unlock()
   160  		l.termiosMu.Unlock()
   161  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   162  	} else {
   163  		l.termiosMu.Unlock()
   164  	}
   165  
   166  	return 0, err
   167  }
   168  
   169  func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error {
   170  	l.sizeMu.Lock()
   171  	defer l.sizeMu.Unlock()
   172  	_, err := l.size.CopyOut(t, args[2].Pointer())
   173  	return err
   174  }
   175  
   176  func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error {
   177  	l.sizeMu.Lock()
   178  	defer l.sizeMu.Unlock()
   179  	_, err := l.size.CopyIn(t, args[2].Pointer())
   180  	return err
   181  }
   182  
   183  func (l *lineDiscipline) masterReadiness() waiter.EventMask {
   184  	// The master termios is immutable so termiosMu is not needed.
   185  	res := l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios)
   186  	l.termiosMu.RLock()
   187  	if l.numReplicas == 0 {
   188  		res |= waiter.EventHUp
   189  	}
   190  	l.termiosMu.RUnlock()
   191  	return res
   192  }
   193  
   194  func (l *lineDiscipline) replicaReadiness() waiter.EventMask {
   195  	l.termiosMu.RLock()
   196  	defer l.termiosMu.RUnlock()
   197  	return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios)
   198  }
   199  
   200  func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error {
   201  	return l.inQueue.readableSize(t, io, args)
   202  }
   203  
   204  func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   205  	l.termiosMu.RLock()
   206  	n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l)
   207  	isCanon := l.termios.LEnabled(linux.ICANON)
   208  	l.termiosMu.RUnlock()
   209  	if err != nil {
   210  		return 0, err
   211  	}
   212  	if n > 0 {
   213  		if notifyEcho {
   214  			l.masterWaiter.Notify(waiter.ReadableEvents | waiter.WritableEvents)
   215  		} else {
   216  			l.masterWaiter.Notify(waiter.WritableEvents)
   217  		}
   218  		if pushed {
   219  			l.replicaWaiter.Notify(waiter.ReadableEvents)
   220  		}
   221  		return n, nil
   222  	}
   223  	if notifyEcho {
   224  		l.masterWaiter.Notify(waiter.ReadableEvents)
   225  	}
   226  	if !pushed && isCanon {
   227  		return 0, nil // EOF
   228  	}
   229  
   230  	return 0, linuxerr.ErrWouldBlock
   231  }
   232  
   233  func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   234  	l.termiosMu.RLock()
   235  	n, notifyEcho, err := l.inQueue.write(ctx, src, l)
   236  	l.termiosMu.RUnlock()
   237  	if err != nil {
   238  		return 0, err
   239  	}
   240  	if notifyEcho {
   241  		l.masterWaiter.Notify(waiter.ReadableEvents)
   242  	}
   243  	if n > 0 {
   244  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   245  		return n, nil
   246  	}
   247  	return 0, linuxerr.ErrWouldBlock
   248  }
   249  
   250  func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error {
   251  	return l.outQueue.readableSize(t, io, args)
   252  }
   253  
   254  func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   255  	l.termiosMu.RLock()
   256  	// Ignore notifyEcho, as it cannot happen when reading from the output queue.
   257  	n, pushed, _, err := l.outQueue.read(ctx, dst, l)
   258  	l.termiosMu.RUnlock()
   259  	if err != nil {
   260  		return 0, err
   261  	}
   262  	if n > 0 {
   263  		l.replicaWaiter.Notify(waiter.WritableEvents)
   264  		if pushed {
   265  			l.masterWaiter.Notify(waiter.ReadableEvents)
   266  		}
   267  		return n, nil
   268  	}
   269  	return 0, linuxerr.ErrWouldBlock
   270  }
   271  
   272  func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   273  	l.termiosMu.RLock()
   274  	// Ignore notifyEcho, as it cannot happen when writing to the output queue.
   275  	n, _, err := l.outQueue.write(ctx, src, l)
   276  	l.termiosMu.RUnlock()
   277  	if err != nil {
   278  		return 0, err
   279  	}
   280  	l.masterWaiter.Notify(waiter.ReadableEvents)
   281  	return n, nil
   282  }
   283  
   284  // replicaOpen is called when a replica file descriptor is opened.
   285  func (l *lineDiscipline) replicaOpen() {
   286  	l.termiosMu.Lock()
   287  	defer l.termiosMu.Unlock()
   288  	l.numReplicas++
   289  }
   290  
   291  // replicaClose is called when a replica file descriptor is closed.
   292  func (l *lineDiscipline) replicaClose() {
   293  	l.termiosMu.Lock()
   294  	l.numReplicas--
   295  	notify := l.numReplicas == 0
   296  	l.termiosMu.Unlock()
   297  	if notify {
   298  		l.masterWaiter.Notify(waiter.EventHUp)
   299  	}
   300  }
   301  
   302  // transformer is a helper interface to make it easier to stateify queue.
   303  type transformer interface {
   304  	// transform functions require queue's mutex to be held.
   305  	// The boolean indicates whether there was any echoed bytes.
   306  	transform(*lineDiscipline, *queue, []byte) (int, bool)
   307  }
   308  
   309  // outputQueueTransformer implements transformer. It performs line discipline
   310  // transformations on the output queue.
   311  //
   312  // +stateify savable
   313  type outputQueueTransformer struct{}
   314  
   315  // transform does output processing for one end of the pty. See
   316  // drivers/tty/n_tty.c:do_output_char for an analogous kernel function.
   317  //
   318  // Preconditions:
   319  //   - l.termiosMu must be held for reading.
   320  //   - q.mu must be held.
   321  func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) {
   322  	// transformOutput is effectively always in noncanonical mode, as the
   323  	// master termios never has ICANON set.
   324  	sizeBudget := nonCanonMaxBytes - len(q.readBuf)
   325  	if sizeBudget <= 0 {
   326  		return 0, false
   327  	}
   328  
   329  	if !l.termios.OEnabled(linux.OPOST) {
   330  		copySize := min(len(buf), sizeBudget)
   331  		q.readBuf = append(q.readBuf, buf[:copySize]...)
   332  		if len(q.readBuf) > 0 {
   333  			q.readable = true
   334  		}
   335  		return copySize, false
   336  	}
   337  
   338  	var ret int
   339  
   340  Outer:
   341  	for ; len(buf) > 0 && sizeBudget > 0; sizeBudget = nonCanonMaxBytes - len(q.readBuf) {
   342  		size := l.peek(buf)
   343  		if size > sizeBudget {
   344  			break Outer
   345  		}
   346  		cBytes := append([]byte{}, buf[:size]...)
   347  		buf = buf[size:]
   348  		// We're guaranteed that cBytes has at least one element.
   349  	cByteSwitch:
   350  		switch cBytes[0] {
   351  		case '\n':
   352  			if l.termios.OEnabled(linux.ONLRET) {
   353  				l.column = 0
   354  			}
   355  			if l.termios.OEnabled(linux.ONLCR) {
   356  				if sizeBudget < 2 {
   357  					break Outer
   358  				}
   359  				ret += size
   360  				q.readBuf = append(q.readBuf, '\r', '\n')
   361  				continue Outer
   362  			}
   363  		case '\r':
   364  			if l.termios.OEnabled(linux.ONOCR) && l.column == 0 {
   365  				// Treat the carriage return as processed, since it's a no-op.
   366  				ret += size
   367  				continue Outer
   368  			}
   369  			if l.termios.OEnabled(linux.OCRNL) {
   370  				cBytes[0] = '\n'
   371  				if l.termios.OEnabled(linux.ONLRET) {
   372  					l.column = 0
   373  				}
   374  				break cByteSwitch
   375  			}
   376  			l.column = 0
   377  		case '\t':
   378  			spaces := spacesPerTab - l.column%spacesPerTab
   379  			if l.termios.OutputFlags&linux.TABDLY == linux.XTABS {
   380  				if sizeBudget < spacesPerTab {
   381  					break Outer
   382  				}
   383  				ret += size
   384  				l.column += spaces
   385  				q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...)
   386  				continue Outer
   387  			}
   388  			l.column += spaces
   389  		case '\b':
   390  			if l.column > 0 {
   391  				l.column--
   392  			}
   393  		default:
   394  			l.column++
   395  		}
   396  		ret += size
   397  		q.readBuf = append(q.readBuf, cBytes...)
   398  	}
   399  	if len(q.readBuf) > 0 {
   400  		q.readable = true
   401  	}
   402  	return ret, false
   403  }
   404  
   405  // inputQueueTransformer implements transformer. It performs line discipline
   406  // transformations on the input queue.
   407  //
   408  // +stateify savable
   409  type inputQueueTransformer struct{}
   410  
   411  // transform does input processing for one end of the pty. Characters read are
   412  // transformed according to flags set in the termios struct. See
   413  // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel
   414  // function.
   415  // It returns an extra boolean indicating whether any characters need to be
   416  // echoed, in which case we need to notify readers.
   417  //
   418  // Preconditions:
   419  //   - l.termiosMu must be held for reading.
   420  //   - q.mu must be held.
   421  func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) {
   422  	// If there's a line waiting to be read in canonical mode, don't write
   423  	// anything else to the read buffer.
   424  	if l.termios.LEnabled(linux.ICANON) && q.readable {
   425  		return 0, false
   426  	}
   427  
   428  	maxBytes := nonCanonMaxBytes
   429  	if l.termios.LEnabled(linux.ICANON) {
   430  		maxBytes = canonMaxBytes
   431  	}
   432  
   433  	var ret int
   434  	var notifyEcho bool
   435  	for len(buf) > 0 && len(q.readBuf) < canonMaxBytes {
   436  		size := l.peek(buf)
   437  		cBytes := append([]byte{}, buf[:size]...)
   438  		// We're guaranteed that cBytes has at least one element.
   439  		switch cBytes[0] {
   440  		case '\r':
   441  			if l.termios.IEnabled(linux.IGNCR) {
   442  				buf = buf[size:]
   443  				ret += size
   444  				continue
   445  			}
   446  			if l.termios.IEnabled(linux.ICRNL) {
   447  				cBytes[0] = '\n'
   448  			}
   449  		case '\n':
   450  			if l.termios.IEnabled(linux.INLCR) {
   451  				cBytes[0] = '\r'
   452  			}
   453  		case l.termios.ControlCharacters[linux.VINTR]: // ctrl-c
   454  			// The input queue is reading from the master TTY and
   455  			// writing to the replica TTY which is connected to the
   456  			// interactive program (like bash). We want to send the
   457  			// signal the process connected to the replica TTY.
   458  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGINT))
   459  		case l.termios.ControlCharacters[linux.VSUSP]: // ctrl-z
   460  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGTSTP))
   461  		case l.termios.ControlCharacters[linux.VQUIT]: // ctrl-\
   462  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGQUIT))
   463  
   464  		// In canonical mode, some characters need to be handled specially; for example, backspace.
   465  		// This roughly aligns with n_tty.c:n_tty_receive_char_canon and n_tty.c:eraser
   466  		// cBytes[0] == ControlCharacters[linux.VKILL] is also handled by n_tty.c:eraser, but this isn't implemented
   467  		case l.termios.ControlCharacters[linux.VWERASE]:
   468  			if !l.termios.LEnabled(linux.IEXTEN) {
   469  				break
   470  			}
   471  			fallthrough
   472  		case l.termios.ControlCharacters[linux.VERASE]:
   473  			if !l.termios.LEnabled(linux.ICANON) {
   474  				break
   475  			}
   476  
   477  			c := cBytes[0]
   478  			killType := linux.VERASE
   479  			if c == l.termios.ControlCharacters[linux.VWERASE] {
   480  				killType = linux.VWERASE
   481  			}
   482  			seenAlphanumeric := false
   483  			for len(q.readBuf) > 0 {
   484  				// Erase a character. If IUTF8 is enabled, erase an entire multibyte unicode character.
   485  				var toErase byte
   486  				cnt := 0
   487  				isContinuationByte := true
   488  				for ; cnt < len(q.readBuf) && isContinuationByte; cnt++ {
   489  					toErase = q.readBuf[len(q.readBuf)-cnt-1]
   490  					isContinuationByte = l.termios.IEnabled(linux.IUTF8) && (toErase&0xc0) == 0x80
   491  				}
   492  				if isContinuationByte {
   493  					// Do not partially erase a multibyte unicode character.
   494  					break
   495  				}
   496  
   497  				// VWERASE will continue erasing characters until we encounter the first non-alphanumeric character
   498  				// that follows some alphanumeric character. We consider "_" to be alphanumeric.
   499  				if killType == linux.VWERASE {
   500  					if unicode.IsLetter(rune(toErase)) || unicode.IsDigit(rune(toErase)) || toErase == '_' {
   501  						seenAlphanumeric = true
   502  					} else if seenAlphanumeric {
   503  						break
   504  					}
   505  				}
   506  
   507  				q.readBuf = q.readBuf[:len(q.readBuf)-cnt]
   508  				if l.termios.LEnabled(linux.ECHO) {
   509  					if l.termios.LEnabled(linux.ECHOPRT) {
   510  						// Not implemented
   511  					} else if killType == linux.VERASE && !l.termios.LEnabled(linux.ECHOE) {
   512  						// Not implemented
   513  					} else if toErase == '\t' {
   514  						// Not implemented
   515  					} else {
   516  						const unicodeDelete byte = 0x7f
   517  						isCtrl := toErase < 0x20 || toErase == unicodeDelete
   518  						echoctl := l.termios.LEnabled(linux.ECHOCTL)
   519  
   520  						charsToDelete := 1
   521  						if isCtrl {
   522  							// echoctl controls how we echo control characters, which also determines how we delete them.
   523  							if echoctl {
   524  								// echoctl echoes control characters as ^X, so we need to erase two characters.
   525  								charsToDelete = 2
   526  							} else {
   527  								// if echoctl is disabled, we don't echo control characters so we don't have to erase anything.
   528  								charsToDelete = 0
   529  							}
   530  						}
   531  						for i := 0; i < charsToDelete; i++ {
   532  							// Linux's kernel does character deletion with this sequence
   533  							// of bytes, presumably because some older terminals don't erase
   534  							// characters with \b, so we need to "erase" the old character
   535  							// by writing a space over it.
   536  							l.outQueue.writeBytes([]byte{'\b', ' ', '\b'}, l)
   537  						}
   538  					}
   539  				}
   540  
   541  				// VERASE only erases a single character
   542  				if killType == linux.VERASE {
   543  					break
   544  				}
   545  			}
   546  
   547  			buf = buf[1:]
   548  			ret += 1
   549  			notifyEcho = true
   550  			continue
   551  		}
   552  
   553  		// In canonical mode, we discard non-terminating characters
   554  		// after the first 4095.
   555  		if l.shouldDiscard(q, cBytes) {
   556  			buf = buf[size:]
   557  			ret += size
   558  			continue
   559  		}
   560  
   561  		// Stop if the buffer would be overfilled.
   562  		if len(q.readBuf)+size > maxBytes {
   563  			break
   564  		}
   565  		buf = buf[size:]
   566  		ret += size
   567  
   568  		// If we get EOF, make the buffer available for reading.
   569  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) {
   570  			q.readable = true
   571  			break
   572  		}
   573  
   574  		q.readBuf = append(q.readBuf, cBytes...)
   575  
   576  		// Anything written to the readBuf will have to be echoed.
   577  		if l.termios.LEnabled(linux.ECHO) {
   578  			l.outQueue.writeBytes(cBytes, l)
   579  			notifyEcho = true
   580  		}
   581  
   582  		// If we finish a line, make it available for reading.
   583  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) {
   584  			q.readable = true
   585  			break
   586  		}
   587  	}
   588  
   589  	// In noncanonical mode, everything is readable.
   590  	if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 {
   591  		q.readable = true
   592  	}
   593  
   594  	return ret, notifyEcho
   595  }
   596  
   597  // shouldDiscard returns whether c should be discarded. In canonical mode, if
   598  // too many bytes are enqueued, we keep reading input and discarding it until
   599  // we find a terminating character. Signal/echo processing still occurs.
   600  //
   601  // Precondition:
   602  //   - l.termiosMu must be held for reading.
   603  //   - q.mu must be held.
   604  func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool {
   605  	return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes)
   606  }
   607  
   608  // peek returns the size in bytes of the next character to process. As long as
   609  // b isn't empty, peek returns a value of at least 1.
   610  func (l *lineDiscipline) peek(b []byte) int {
   611  	size := 1
   612  	// If UTF-8 support is enabled, runes might be multiple bytes.
   613  	if l.termios.IEnabled(linux.IUTF8) {
   614  		_, size = utf8.DecodeRune(b)
   615  	}
   616  	return size
   617  }