github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/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/MerlinKodo/gvisor/pkg/abi/linux"
    22  	"github.com/MerlinKodo/gvisor/pkg/context"
    23  	"github.com/MerlinKodo/gvisor/pkg/errors/linuxerr"
    24  	"github.com/MerlinKodo/gvisor/pkg/sentry/arch"
    25  	"github.com/MerlinKodo/gvisor/pkg/sentry/kernel"
    26  	"github.com/MerlinKodo/gvisor/pkg/sync"
    27  	"github.com/MerlinKodo/gvisor/pkg/usermem"
    28  	"github.com/MerlinKodo/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  //
    67  // masterFD                                                           replicaFD
    68  //
    69  //	^                                                                   |
    70  //	|                                                                   |
    71  //	|   output to terminal   +--------------+    output from process    |
    72  //	+------------------------| output queue |<--------------------------+
    73  //	    (outputQueueRead)    +--------------+    (outputQueueWrite)
    74  //
    75  // There is special handling for the ECHO option, where bytes written to the
    76  // input queue are also output back to the terminal by being written to
    77  // l.outQueue by the input queue transformer.
    78  //
    79  // Lock order:
    80  //
    81  //	termiosMu
    82  //	  inQueue.mu
    83  //	    outQueue.mu
    84  //
    85  // +stateify savable
    86  type lineDiscipline struct {
    87  	// sizeMu protects size.
    88  	sizeMu sync.Mutex `state:"nosave"`
    89  
    90  	// size is the terminal size (width and height).
    91  	size linux.WindowSize
    92  
    93  	// inQueue is the input queue of the terminal.
    94  	inQueue queue
    95  
    96  	// outQueue is the output queue of the terminal.
    97  	outQueue queue
    98  
    99  	// termiosMu protects termios.
   100  	termiosMu sync.RWMutex `state:"nosave"`
   101  
   102  	// termios is the terminal configuration used by the lineDiscipline.
   103  	termios linux.KernelTermios
   104  
   105  	// column is the location in a row of the cursor. This is important for
   106  	// handling certain special characters like backspace.
   107  	column int
   108  
   109  	// numReplicas is the number of replica file descriptors.
   110  	numReplicas int
   111  
   112  	// masterWaiter is used to wait on the master end of the TTY.
   113  	masterWaiter waiter.Queue
   114  
   115  	// replicaWaiter is used to wait on the replica end of the TTY.
   116  	replicaWaiter waiter.Queue
   117  
   118  	// terminal is the terminal linked to this lineDiscipline.
   119  	terminal *Terminal
   120  }
   121  
   122  func newLineDiscipline(termios linux.KernelTermios, terminal *Terminal) *lineDiscipline {
   123  	ld := lineDiscipline{
   124  		termios:  termios,
   125  		terminal: terminal,
   126  	}
   127  	ld.inQueue.transformer = &inputQueueTransformer{}
   128  	ld.outQueue.transformer = &outputQueueTransformer{}
   129  	return &ld
   130  }
   131  
   132  // getTermios gets the linux.Termios for the tty.
   133  func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   134  	l.termiosMu.RLock()
   135  	defer l.termiosMu.RUnlock()
   136  	// We must copy a Termios struct, not KernelTermios.
   137  	t := l.termios.ToTermios()
   138  	_, err := t.CopyOut(task, args[2].Pointer())
   139  	return 0, err
   140  }
   141  
   142  // setTermios sets a linux.Termios for the tty.
   143  func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) {
   144  	l.termiosMu.Lock()
   145  	oldCanonEnabled := l.termios.LEnabled(linux.ICANON)
   146  	// We must copy a Termios struct, not KernelTermios.
   147  	var t linux.Termios
   148  	_, err := t.CopyIn(task, args[2].Pointer())
   149  	l.termios.FromTermios(t)
   150  
   151  	// If canonical mode is turned off, move bytes from inQueue's wait
   152  	// buffer to its read buffer. Anything already in the read buffer is
   153  	// now readable.
   154  	if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) {
   155  		l.inQueue.mu.Lock()
   156  		l.inQueue.pushWaitBufLocked(l)
   157  		l.inQueue.readable = true
   158  		l.inQueue.mu.Unlock()
   159  		l.termiosMu.Unlock()
   160  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   161  	} else {
   162  		l.termiosMu.Unlock()
   163  	}
   164  
   165  	return 0, err
   166  }
   167  
   168  func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error {
   169  	l.sizeMu.Lock()
   170  	defer l.sizeMu.Unlock()
   171  	_, err := l.size.CopyOut(t, args[2].Pointer())
   172  	return err
   173  }
   174  
   175  func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error {
   176  	l.sizeMu.Lock()
   177  	defer l.sizeMu.Unlock()
   178  	_, err := l.size.CopyIn(t, args[2].Pointer())
   179  	return err
   180  }
   181  
   182  func (l *lineDiscipline) masterReadiness() waiter.EventMask {
   183  	// We don't have to lock a termios because the default master termios
   184  	// is immutable.
   185  	return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios)
   186  }
   187  
   188  func (l *lineDiscipline) replicaReadiness() waiter.EventMask {
   189  	l.termiosMu.RLock()
   190  	defer l.termiosMu.RUnlock()
   191  	return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios)
   192  }
   193  
   194  func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error {
   195  	return l.inQueue.readableSize(t, io, args)
   196  }
   197  
   198  func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   199  	l.termiosMu.RLock()
   200  	n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l)
   201  	isCanon := l.termios.LEnabled(linux.ICANON)
   202  	l.termiosMu.RUnlock()
   203  	if err != nil {
   204  		return 0, err
   205  	}
   206  	if n > 0 {
   207  		if notifyEcho {
   208  			l.masterWaiter.Notify(waiter.ReadableEvents | waiter.WritableEvents)
   209  		} else {
   210  			l.masterWaiter.Notify(waiter.WritableEvents)
   211  		}
   212  		if pushed {
   213  			l.replicaWaiter.Notify(waiter.ReadableEvents)
   214  		}
   215  		return n, nil
   216  	}
   217  	if notifyEcho {
   218  		l.masterWaiter.Notify(waiter.ReadableEvents)
   219  	}
   220  	if !pushed && isCanon {
   221  		return 0, nil // EOF
   222  	}
   223  
   224  	return 0, linuxerr.ErrWouldBlock
   225  }
   226  
   227  func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   228  	l.termiosMu.RLock()
   229  	n, notifyEcho, err := l.inQueue.write(ctx, src, l)
   230  	l.termiosMu.RUnlock()
   231  	if err != nil {
   232  		return 0, err
   233  	}
   234  	if notifyEcho {
   235  		l.masterWaiter.Notify(waiter.ReadableEvents)
   236  	}
   237  	if n > 0 {
   238  		l.replicaWaiter.Notify(waiter.ReadableEvents)
   239  		return n, nil
   240  	}
   241  	return 0, linuxerr.ErrWouldBlock
   242  }
   243  
   244  func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error {
   245  	return l.outQueue.readableSize(t, io, args)
   246  }
   247  
   248  func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) {
   249  	l.termiosMu.RLock()
   250  	// Ignore notifyEcho, as it cannot happen when reading from the output queue.
   251  	n, pushed, _, err := l.outQueue.read(ctx, dst, l)
   252  	l.termiosMu.RUnlock()
   253  	if err != nil {
   254  		return 0, err
   255  	}
   256  	if n > 0 {
   257  		l.replicaWaiter.Notify(waiter.WritableEvents)
   258  		if pushed {
   259  			l.masterWaiter.Notify(waiter.ReadableEvents)
   260  		}
   261  		return n, nil
   262  	}
   263  	return 0, linuxerr.ErrWouldBlock
   264  }
   265  
   266  func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) {
   267  	l.termiosMu.RLock()
   268  	// Ignore notifyEcho, as it cannot happen when writing to the output queue.
   269  	n, _, err := l.outQueue.write(ctx, src, l)
   270  	l.termiosMu.RUnlock()
   271  	if err != nil {
   272  		return 0, err
   273  	}
   274  	if n > 0 {
   275  		l.masterWaiter.Notify(waiter.ReadableEvents)
   276  		return n, nil
   277  	}
   278  	return 0, linuxerr.ErrWouldBlock
   279  }
   280  
   281  // replicaOpen is called when a replica file descriptor is opened.
   282  func (l *lineDiscipline) replicaOpen() {
   283  	l.termiosMu.Lock()
   284  	defer l.termiosMu.Unlock()
   285  	l.numReplicas++
   286  }
   287  
   288  // replicaClose is called when a replica file descriptor is closed.
   289  func (l *lineDiscipline) replicaClose() {
   290  	l.termiosMu.Lock()
   291  	defer l.termiosMu.Unlock()
   292  	l.numReplicas--
   293  }
   294  
   295  // transformer is a helper interface to make it easier to stateify queue.
   296  type transformer interface {
   297  	// transform functions require queue's mutex to be held.
   298  	// The boolean indicates whether there was any echoed bytes.
   299  	transform(*lineDiscipline, *queue, []byte) (int, bool)
   300  }
   301  
   302  // outputQueueTransformer implements transformer. It performs line discipline
   303  // transformations on the output queue.
   304  //
   305  // +stateify savable
   306  type outputQueueTransformer struct{}
   307  
   308  // transform does output processing for one end of the pty. See
   309  // drivers/tty/n_tty.c:do_output_char for an analogous kernel function.
   310  //
   311  // Preconditions:
   312  //   - l.termiosMu must be held for reading.
   313  //   - q.mu must be held.
   314  func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) {
   315  	// transformOutput is effectively always in noncanonical mode, as the
   316  	// master termios never has ICANON set.
   317  
   318  	if !l.termios.OEnabled(linux.OPOST) {
   319  		q.readBuf = append(q.readBuf, buf...)
   320  		if len(q.readBuf) > 0 {
   321  			q.readable = true
   322  		}
   323  		return len(buf), false
   324  	}
   325  
   326  	var ret int
   327  	for len(buf) > 0 {
   328  		size := l.peek(buf)
   329  		cBytes := append([]byte{}, buf[:size]...)
   330  		ret += size
   331  		buf = buf[size:]
   332  		// We're guaranteed that cBytes has at least one element.
   333  		switch cBytes[0] {
   334  		case '\n':
   335  			if l.termios.OEnabled(linux.ONLRET) {
   336  				l.column = 0
   337  			}
   338  			if l.termios.OEnabled(linux.ONLCR) {
   339  				q.readBuf = append(q.readBuf, '\r', '\n')
   340  				continue
   341  			}
   342  		case '\r':
   343  			if l.termios.OEnabled(linux.ONOCR) && l.column == 0 {
   344  				continue
   345  			}
   346  			if l.termios.OEnabled(linux.OCRNL) {
   347  				cBytes[0] = '\n'
   348  				if l.termios.OEnabled(linux.ONLRET) {
   349  					l.column = 0
   350  				}
   351  				break
   352  			}
   353  			l.column = 0
   354  		case '\t':
   355  			spaces := spacesPerTab - l.column%spacesPerTab
   356  			if l.termios.OutputFlags&linux.TABDLY == linux.XTABS {
   357  				l.column += spaces
   358  				q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...)
   359  				continue
   360  			}
   361  			l.column += spaces
   362  		case '\b':
   363  			if l.column > 0 {
   364  				l.column--
   365  			}
   366  		default:
   367  			l.column++
   368  		}
   369  		q.readBuf = append(q.readBuf, cBytes...)
   370  	}
   371  	if len(q.readBuf) > 0 {
   372  		q.readable = true
   373  	}
   374  	return ret, false
   375  }
   376  
   377  // inputQueueTransformer implements transformer. It performs line discipline
   378  // transformations on the input queue.
   379  //
   380  // +stateify savable
   381  type inputQueueTransformer struct{}
   382  
   383  // transform does input processing for one end of the pty. Characters read are
   384  // transformed according to flags set in the termios struct. See
   385  // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel
   386  // function.
   387  // It returns an extra boolean indicating whether any characters need to be
   388  // echoed, in which case we need to notify readers.
   389  //
   390  // Preconditions:
   391  //   - l.termiosMu must be held for reading.
   392  //   - q.mu must be held.
   393  func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) {
   394  	// If there's a line waiting to be read in canonical mode, don't write
   395  	// anything else to the read buffer.
   396  	if l.termios.LEnabled(linux.ICANON) && q.readable {
   397  		return 0, false
   398  	}
   399  
   400  	maxBytes := nonCanonMaxBytes
   401  	if l.termios.LEnabled(linux.ICANON) {
   402  		maxBytes = canonMaxBytes
   403  	}
   404  
   405  	var ret int
   406  	var notifyEcho bool
   407  	for len(buf) > 0 && len(q.readBuf) < canonMaxBytes {
   408  		size := l.peek(buf)
   409  		cBytes := append([]byte{}, buf[:size]...)
   410  		// We're guaranteed that cBytes has at least one element.
   411  		switch cBytes[0] {
   412  		case '\r':
   413  			if l.termios.IEnabled(linux.IGNCR) {
   414  				buf = buf[size:]
   415  				ret += size
   416  				continue
   417  			}
   418  			if l.termios.IEnabled(linux.ICRNL) {
   419  				cBytes[0] = '\n'
   420  			}
   421  		case '\n':
   422  			if l.termios.IEnabled(linux.INLCR) {
   423  				cBytes[0] = '\r'
   424  			}
   425  		case l.termios.ControlCharacters[linux.VINTR]: // ctrl-c
   426  			// The input queue is reading from the master TTY and
   427  			// writing to the replica TTY which is connected to the
   428  			// interactive program (like bash). We want to send the
   429  			// signal the process connected to the replica TTY.
   430  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGINT))
   431  		case l.termios.ControlCharacters[linux.VSUSP]: // ctrl-z
   432  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGTSTP))
   433  		case l.termios.ControlCharacters[linux.VQUIT]: // ctrl-\
   434  			l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGQUIT))
   435  		}
   436  
   437  		// In canonical mode, we discard non-terminating characters
   438  		// after the first 4095.
   439  		if l.shouldDiscard(q, cBytes) {
   440  			buf = buf[size:]
   441  			ret += size
   442  			continue
   443  		}
   444  
   445  		// Stop if the buffer would be overfilled.
   446  		if len(q.readBuf)+size > maxBytes {
   447  			break
   448  		}
   449  		buf = buf[size:]
   450  		ret += size
   451  
   452  		// If we get EOF, make the buffer available for reading.
   453  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) {
   454  			q.readable = true
   455  			break
   456  		}
   457  
   458  		q.readBuf = append(q.readBuf, cBytes...)
   459  
   460  		// Anything written to the readBuf will have to be echoed.
   461  		if l.termios.LEnabled(linux.ECHO) {
   462  			l.outQueue.writeBytes(cBytes, l)
   463  			notifyEcho = true
   464  		}
   465  
   466  		// If we finish a line, make it available for reading.
   467  		if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) {
   468  			q.readable = true
   469  			break
   470  		}
   471  	}
   472  
   473  	// In noncanonical mode, everything is readable.
   474  	if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 {
   475  		q.readable = true
   476  	}
   477  
   478  	return ret, notifyEcho
   479  }
   480  
   481  // shouldDiscard returns whether c should be discarded. In canonical mode, if
   482  // too many bytes are enqueued, we keep reading input and discarding it until
   483  // we find a terminating character. Signal/echo processing still occurs.
   484  //
   485  // Precondition:
   486  //   - l.termiosMu must be held for reading.
   487  //   - q.mu must be held.
   488  func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool {
   489  	return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes)
   490  }
   491  
   492  // peek returns the size in bytes of the next character to process. As long as
   493  // b isn't empty, peek returns a value of at least 1.
   494  func (l *lineDiscipline) peek(b []byte) int {
   495  	size := 1
   496  	// If UTF-8 support is enabled, runes might be multiple bytes.
   497  	if l.termios.IEnabled(linux.IUTF8) {
   498  		_, size = utf8.DecodeRune(b)
   499  	}
   500  	return size
   501  }