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