github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/tty/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 tty
    16  
    17  import (
    18  	"bytes"
    19  	"unicode/utf8"
    20  
    21  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    22  	"github.com/SagerNet/gvisor/pkg/context"
    23  	"github.com/SagerNet/gvisor/pkg/sentry/arch"
    24  	"github.com/SagerNet/gvisor/pkg/sentry/kernel"
    25  	"github.com/SagerNet/gvisor/pkg/sync"
    26  	"github.com/SagerNet/gvisor/pkg/syserror"
    27  	"github.com/SagerNet/gvisor/pkg/usermem"
    28  	"github.com/SagerNet/gvisor/pkg/waiter"
    29  )
    30  
    31  // LINT.IfChange
    32  
    33  const (
    34  	// canonMaxBytes is the number of bytes that fit into a single line of
    35  	// terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE
    36  	// in include/linux/tty.h.
    37  	canonMaxBytes = 4096
    38  
    39  	// nonCanonMaxBytes is the maximum number of bytes that can be read at
    40  	// a time in noncanonical mode.
    41  	nonCanonMaxBytes = canonMaxBytes - 1
    42  
    43  	spacesPerTab = 8
    44  )
    45  
    46  // lineDiscipline dictates how input and output are handled between the
    47  // pseudoterminal (pty) master and replica. It can be configured to alter I/O,
    48  // modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man
    49  // pages are good resources for how to affect the line discipline:
    50  //
    51  //   * termios(3)
    52  //   * tty_ioctl(4)
    53  //
    54  // This file corresponds most closely to drivers/tty/n_tty.c.
    55  //
    56  // lineDiscipline has a simple structure but supports a multitude of options
    57  // (see the above man pages). It consists of two queues of bytes: one from the
    58  // terminal master to replica (the input queue) and one from replica to master
    59  // (the output queue). When bytes are written to one end of the pty, the line
    60  // discipline reads the bytes, modifies them or takes special action if
    61  // required, and enqueues them to be read by the other end of the pty:
    62  //
    63  //       input from terminal    +-------------+   input to process (e.g. bash)
    64  //    +------------------------>| input queue |---------------------------+
    65  //    |   (inputQueueWrite)     +-------------+     (inputQueueRead)      |
    66  //    |                                                                   |
    67  //    |                                                                   v
    68  // masterFD                                                           replicaFD
    69  //    ^                                                                   |
    70  //    |                                                                   |
    71  //    |   output to terminal   +--------------+    output from process    |
    72  //    +------------------------| output queue |<--------------------------+
    73  //        (outputQueueRead)    +--------------+    (outputQueueWrite)
    74  //
    75  // Lock order:
    76  //  termiosMu
    77  //    inQueue.mu
    78  //      outQueue.mu
    79  //
    80  // +stateify savable
    81  type lineDiscipline struct {
    82  	// sizeMu protects size.
    83  	sizeMu sync.Mutex `state:"nosave"`
    84  
    85  	// size is the terminal size (width and height).
    86  	size linux.WindowSize
    87  
    88  	// inQueue is the input queue of the terminal.
    89  	inQueue queue
    90  
    91  	// outQueue is the output queue of the terminal.
    92  	outQueue queue
    93  
    94  	// termiosMu protects termios.
    95  	termiosMu sync.RWMutex `state:"nosave"`
    96  
    97  	// termios is the terminal configuration used by the lineDiscipline.
    98  	termios linux.KernelTermios
    99  
   100  	// column is the location in a row of the cursor. This is important for
   101  	// handling certain special characters like backspace.
   102  	column int
   103  
   104  	// masterWaiter is used to wait on the master end of the TTY.
   105  	masterWaiter waiter.Queue `state:"zerovalue"`
   106  
   107  	// replicaWaiter is used to wait on the replica end of the TTY.
   108  	replicaWaiter waiter.Queue `state:"zerovalue"`
   109  }
   110  
   111  func newLineDiscipline(termios linux.KernelTermios) *lineDiscipline {
   112  	ld := lineDiscipline{termios: termios}
   113  	ld.inQueue.transformer = &inputQueueTransformer{}
   114  	ld.outQueue.transformer = &outputQueueTransformer{}
   115  	return &ld
   116  }
   117  
   118  // getTermios gets the linux.Termios for the tty.
   119  func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   120  	l.termiosMu.RLock()
   121  	defer l.termiosMu.RUnlock()
   122  	// We must copy a Termios struct, not KernelTermios.
   123  	t := l.termios.ToTermios()
   124  	_, err := t.CopyOut(task, args[2].Pointer())
   125  	return 0, err
   126  }
   127  
   128  // setTermios sets a linux.Termios for the tty.
   129  func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   130  	l.termiosMu.Lock()
   131  	defer l.termiosMu.Unlock()
   132  	oldCanonEnabled := l.termios.LEnabled(linux.ICANON)
   133  	// We must copy a Termios struct, not KernelTermios.
   134  	var t linux.Termios
   135  	_, err := t.CopyIn(task, args[2].Pointer())
   136  	l.termios.FromTermios(t)
   137  
   138  	// If canonical mode is turned off, move bytes from inQueue's wait
   139  	// buffer to its read buffer. Anything already in the read buffer is
   140  	// now readable.
   141  	if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) {
   142  		l.inQueue.mu.Lock()
   143  		l.inQueue.pushWaitBufLocked(l)
   144  		l.inQueue.readable = true
   145  		l.inQueue.mu.Unlock()
   146  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   147  	}
   148  
   149  	return 0, err
   150  }
   151  
   152  func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error {
   153  	l.sizeMu.Lock()
   154  	defer l.sizeMu.Unlock()
   155  	_, err := l.size.CopyOut(t, args[2].Pointer())
   156  	return err
   157  }
   158  
   159  func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error {
   160  	l.sizeMu.Lock()
   161  	defer l.sizeMu.Unlock()
   162  	_, err := l.size.CopyIn(t, args[2].Pointer())
   163  	return err
   164  }
   165  
   166  func (l *lineDiscipline) masterReadiness() waiter.EventMask {
   167  	// We don't have to lock a termios because the default master termios
   168  	// is immutable.
   169  	return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios)
   170  }
   171  
   172  func (l *lineDiscipline) replicaReadiness() waiter.EventMask {
   173  	l.termiosMu.RLock()
   174  	defer l.termiosMu.RUnlock()
   175  	return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios)
   176  }
   177  
   178  func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, args arch.SyscallArguments) error {
   179  	return l.inQueue.readableSize(t, args)
   180  }
   181  
   182  func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   183  	l.termiosMu.RLock()
   184  	defer l.termiosMu.RUnlock()
   185  	n, pushed, err := l.inQueue.read(ctx, dst, l)
   186  	if err != nil {
   187  		return 0, err
   188  	}
   189  	if n > 0 {
   190  		l.masterWaiter.Notify(waiter.WritableEvents)
   191  		if pushed {
   192  			l.replicaWaiter.Notify(waiter.ReadableEvents)
   193  		}
   194  		return n, nil
   195  	}
   196  	return 0, syserror.ErrWouldBlock
   197  }
   198  
   199  func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   200  	l.termiosMu.RLock()
   201  	defer l.termiosMu.RUnlock()
   202  	n, err := l.inQueue.write(ctx, src, l)
   203  	if err != nil {
   204  		return 0, err
   205  	}
   206  	if n > 0 {
   207  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   208  		return n, nil
   209  	}
   210  	return 0, syserror.ErrWouldBlock
   211  }
   212  
   213  func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, args arch.SyscallArguments) error {
   214  	return l.outQueue.readableSize(t, args)
   215  }
   216  
   217  func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   218  	l.termiosMu.RLock()
   219  	defer l.termiosMu.RUnlock()
   220  	n, pushed, err := l.outQueue.read(ctx, dst, l)
   221  	if err != nil {
   222  		return 0, err
   223  	}
   224  	if n > 0 {
   225  		l.replicaWaiter.Notify(waiter.WritableEvents)
   226  		if pushed {
   227  			l.masterWaiter.Notify(waiter.ReadableEvents)
   228  		}
   229  		return n, nil
   230  	}
   231  	return 0, syserror.ErrWouldBlock
   232  }
   233  
   234  func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   235  	l.termiosMu.RLock()
   236  	defer l.termiosMu.RUnlock()
   237  	n, err := l.outQueue.write(ctx, src, l)
   238  	if err != nil {
   239  		return 0, err
   240  	}
   241  	if n > 0 {
   242  		l.masterWaiter.Notify(waiter.ReadableEvents)
   243  		return n, nil
   244  	}
   245  	return 0, syserror.ErrWouldBlock
   246  }
   247  
   248  // transformer is a helper interface to make it easier to stateify queue.
   249  type transformer interface {
   250  	// transform functions require queue's mutex to be held.
   251  	transform(*lineDiscipline, *queue, []byte) int
   252  }
   253  
   254  // outputQueueTransformer implements transformer. It performs line discipline
   255  // transformations on the output queue.
   256  //
   257  // +stateify savable
   258  type outputQueueTransformer struct{}
   259  
   260  // transform does output processing for one end of the pty. See
   261  // drivers/tty/n_tty.c:do_output_char for an analogous kernel function.
   262  //
   263  // Preconditions:
   264  // * l.termiosMu must be held for reading.
   265  // * q.mu must be held.
   266  func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int {
   267  	// transformOutput is effectively always in noncanonical mode, as the
   268  	// master termios never has ICANON set.
   269  
   270  	if !l.termios.OEnabled(linux.OPOST) {
   271  		q.readBuf = append(q.readBuf, buf...)
   272  		if len(q.readBuf) > 0 {
   273  			q.readable = true
   274  		}
   275  		return len(buf)
   276  	}
   277  
   278  	var ret int
   279  	for len(buf) > 0 {
   280  		size := l.peek(buf)
   281  		cBytes := append([]byte{}, buf[:size]...)
   282  		ret += size
   283  		buf = buf[size:]
   284  		// We're guaranteed that cBytes has at least one element.
   285  		switch cBytes[0] {
   286  		case '\n':
   287  			if l.termios.OEnabled(linux.ONLRET) {
   288  				l.column = 0
   289  			}
   290  			if l.termios.OEnabled(linux.ONLCR) {
   291  				q.readBuf = append(q.readBuf, '\r', '\n')
   292  				continue
   293  			}
   294  		case '\r':
   295  			if l.termios.OEnabled(linux.ONOCR) && l.column == 0 {
   296  				continue
   297  			}
   298  			if l.termios.OEnabled(linux.OCRNL) {
   299  				cBytes[0] = '\n'
   300  				if l.termios.OEnabled(linux.ONLRET) {
   301  					l.column = 0
   302  				}
   303  				break
   304  			}
   305  			l.column = 0
   306  		case '\t':
   307  			spaces := spacesPerTab - l.column%spacesPerTab
   308  			if l.termios.OutputFlags&linux.TABDLY == linux.XTABS {
   309  				l.column += spaces
   310  				q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...)
   311  				continue
   312  			}
   313  			l.column += spaces
   314  		case '\b':
   315  			if l.column > 0 {
   316  				l.column--
   317  			}
   318  		default:
   319  			l.column++
   320  		}
   321  		q.readBuf = append(q.readBuf, cBytes...)
   322  	}
   323  	if len(q.readBuf) > 0 {
   324  		q.readable = true
   325  	}
   326  	return ret
   327  }
   328  
   329  // inputQueueTransformer implements transformer. It performs line discipline
   330  // transformations on the input queue.
   331  //
   332  // +stateify savable
   333  type inputQueueTransformer struct{}
   334  
   335  // transform does input processing for one end of the pty. Characters read are
   336  // transformed according to flags set in the termios struct. See
   337  // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel
   338  // function.
   339  //
   340  // Preconditions:
   341  // * l.termiosMu must be held for reading.
   342  // * q.mu must be held.
   343  func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int {
   344  	// If there's a line waiting to be read in canonical mode, don't write
   345  	// anything else to the read buffer.
   346  	if l.termios.LEnabled(linux.ICANON) && q.readable {
   347  		return 0
   348  	}
   349  
   350  	maxBytes := nonCanonMaxBytes
   351  	if l.termios.LEnabled(linux.ICANON) {
   352  		maxBytes = canonMaxBytes
   353  	}
   354  
   355  	var ret int
   356  	for len(buf) > 0 && len(q.readBuf) < canonMaxBytes {
   357  		size := l.peek(buf)
   358  		cBytes := append([]byte{}, buf[:size]...)
   359  		// We're guaranteed that cBytes has at least one element.
   360  		switch cBytes[0] {
   361  		case '\r':
   362  			if l.termios.IEnabled(linux.IGNCR) {
   363  				buf = buf[size:]
   364  				ret += size
   365  				continue
   366  			}
   367  			if l.termios.IEnabled(linux.ICRNL) {
   368  				cBytes[0] = '\n'
   369  			}
   370  		case '\n':
   371  			if l.termios.IEnabled(linux.INLCR) {
   372  				cBytes[0] = '\r'
   373  			}
   374  		}
   375  
   376  		// In canonical mode, we discard non-terminating characters
   377  		// after the first 4095.
   378  		if l.shouldDiscard(q, cBytes) {
   379  			buf = buf[size:]
   380  			ret += size
   381  			continue
   382  		}
   383  
   384  		// Stop if the buffer would be overfilled.
   385  		if len(q.readBuf)+size > maxBytes {
   386  			break
   387  		}
   388  		buf = buf[size:]
   389  		ret += size
   390  
   391  		// If we get EOF, make the buffer available for reading.
   392  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) {
   393  			q.readable = true
   394  			break
   395  		}
   396  
   397  		q.readBuf = append(q.readBuf, cBytes...)
   398  
   399  		// Anything written to the readBuf will have to be echoed.
   400  		if l.termios.LEnabled(linux.ECHO) {
   401  			l.outQueue.writeBytes(cBytes, l)
   402  			l.masterWaiter.Notify(waiter.ReadableEvents)
   403  		}
   404  
   405  		// If we finish a line, make it available for reading.
   406  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) {
   407  			q.readable = true
   408  			break
   409  		}
   410  	}
   411  
   412  	// In noncanonical mode, everything is readable.
   413  	if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 {
   414  		q.readable = true
   415  	}
   416  
   417  	return ret
   418  }
   419  
   420  // shouldDiscard returns whether c should be discarded. In canonical mode, if
   421  // too many bytes are enqueued, we keep reading input and discarding it until
   422  // we find a terminating character. Signal/echo processing still occurs.
   423  //
   424  // Precondition:
   425  // * l.termiosMu must be held for reading.
   426  // * q.mu must be held.
   427  func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool {
   428  	return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes)
   429  }
   430  
   431  // peek returns the size in bytes of the next character to process. As long as
   432  // b isn't empty, peek returns a value of at least 1.
   433  func (l *lineDiscipline) peek(b []byte) int {
   434  	size := 1
   435  	// If UTF-8 support is enabled, runes might be multiple bytes.
   436  	if l.termios.IEnabled(linux.IUTF8) {
   437  		_, size = utf8.DecodeRune(b)
   438  	}
   439  	return size
   440  }
   441  
   442  // LINT.ThenChange(../../fsimpl/devpts/line_discipline.go)