github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/fsimpl/devpts/queue.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  	"github.com/nicocha30/gvisor-ligolo/pkg/abi/linux"
    19  	"github.com/nicocha30/gvisor-ligolo/pkg/context"
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/errors/linuxerr"
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/marshal/primitive"
    22  	"github.com/nicocha30/gvisor-ligolo/pkg/safemem"
    23  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/arch"
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    26  	"github.com/nicocha30/gvisor-ligolo/pkg/usermem"
    27  	"github.com/nicocha30/gvisor-ligolo/pkg/waiter"
    28  )
    29  
    30  // waitBufMaxBytes is the maximum size of a wait buffer. It is based on
    31  // TTYB_DEFAULT_MEM_LIMIT.
    32  const waitBufMaxBytes = 131072
    33  
    34  // queue represents one of the input or output queues between a pty master and
    35  // replica. Bytes written to a queue are added to the read buffer until it is
    36  // full, at which point they are written to the wait buffer. Bytes are
    37  // processed (i.e. undergo termios transformations) as they are added to the
    38  // read buffer. The read buffer is readable when its length is nonzero and
    39  // readable is true.
    40  //
    41  // +stateify savable
    42  type queue struct {
    43  	// mu protects everything in queue.
    44  	mu sync.Mutex `state:"nosave"`
    45  
    46  	// readBuf is buffer of data ready to be read when readable is true.
    47  	// This data has been processed.
    48  	readBuf []byte
    49  
    50  	// waitBuf contains data that can't fit into readBuf. It is put here
    51  	// until it can be loaded into the read buffer. waitBuf contains data
    52  	// that hasn't been processed.
    53  	waitBuf    [][]byte
    54  	waitBufLen uint64
    55  
    56  	// readable indicates whether the read buffer can be read from.  In
    57  	// canonical mode, there can be an unterminated line in the read buffer,
    58  	// so readable must be checked.
    59  	readable bool
    60  
    61  	// transform is the the queue's function for transforming bytes
    62  	// entering the queue. For example, transform might convert all '\r's
    63  	// entering the queue to '\n's.
    64  	transformer
    65  }
    66  
    67  // readReadiness returns whether q is ready to be read from.
    68  func (q *queue) readReadiness(t *linux.KernelTermios) waiter.EventMask {
    69  	q.mu.Lock()
    70  	defer q.mu.Unlock()
    71  	if len(q.readBuf) > 0 && q.readable {
    72  		return waiter.ReadableEvents
    73  	}
    74  	return waiter.EventMask(0)
    75  }
    76  
    77  // writeReadiness returns whether q is ready to be written to.
    78  func (q *queue) writeReadiness(t *linux.KernelTermios) waiter.EventMask {
    79  	q.mu.Lock()
    80  	defer q.mu.Unlock()
    81  	if q.waitBufLen < waitBufMaxBytes {
    82  		return waiter.WritableEvents
    83  	}
    84  	return waiter.EventMask(0)
    85  }
    86  
    87  // readableSize writes the number of readable bytes to userspace.
    88  func (q *queue) readableSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error {
    89  	q.mu.Lock()
    90  	defer q.mu.Unlock()
    91  	size := primitive.Int32(0)
    92  	if q.readable {
    93  		size = primitive.Int32(len(q.readBuf))
    94  	}
    95  
    96  	_, err := size.CopyOut(t, args[2].Pointer())
    97  	return err
    98  
    99  }
   100  
   101  // read reads from q to userspace. It returns:
   102  //   - The number of bytes read
   103  //   - Whether the read caused more readable data to become available (whether
   104  //     data was pushed from the wait buffer to the read buffer).
   105  //   - Whether any data was echoed back (need to notify readers).
   106  //
   107  // Preconditions: l.termiosMu must be held for reading.
   108  func (q *queue) read(ctx context.Context, dst usermem.IOSequence, l *lineDiscipline) (int64, bool, bool, error) {
   109  	q.mu.Lock()
   110  	defer q.mu.Unlock()
   111  
   112  	if !q.readable {
   113  		if l.numReplicas == 0 {
   114  			return 0, false, false, linuxerr.EIO
   115  		}
   116  		return 0, false, false, linuxerr.ErrWouldBlock
   117  	}
   118  
   119  	if dst.NumBytes() > canonMaxBytes {
   120  		dst = dst.TakeFirst(canonMaxBytes)
   121  	}
   122  
   123  	n, err := dst.CopyOutFrom(ctx, safemem.ReaderFunc(func(dst safemem.BlockSeq) (uint64, error) {
   124  		src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(q.readBuf))
   125  		n, err := safemem.CopySeq(dst, src)
   126  		if err != nil {
   127  			return 0, err
   128  		}
   129  		q.readBuf = q.readBuf[n:]
   130  
   131  		// If we read everything, this queue is no longer readable.
   132  		if len(q.readBuf) == 0 {
   133  			q.readable = false
   134  		}
   135  
   136  		return n, nil
   137  	}))
   138  	if err != nil {
   139  		return 0, false, false, err
   140  	}
   141  
   142  	// Move data from the queue's wait buffer to its read buffer.
   143  	nPushed, notifyEcho := q.pushWaitBufLocked(l)
   144  
   145  	return int64(n), nPushed > 0, notifyEcho, nil
   146  }
   147  
   148  // write writes to q from userspace.
   149  // The returned boolean indicates whether any data was echoed back.
   150  //
   151  // Preconditions: l.termiosMu must be held for reading.
   152  func (q *queue) write(ctx context.Context, src usermem.IOSequence, l *lineDiscipline) (int64, bool, error) {
   153  	q.mu.Lock()
   154  	defer q.mu.Unlock()
   155  
   156  	// Copy data into the wait buffer.
   157  	n, err := src.CopyInTo(ctx, safemem.WriterFunc(func(src safemem.BlockSeq) (uint64, error) {
   158  		copyLen := src.NumBytes()
   159  		room := waitBufMaxBytes - q.waitBufLen
   160  		// If out of room, return EAGAIN.
   161  		if room == 0 && copyLen > 0 {
   162  			return 0, linuxerr.ErrWouldBlock
   163  		}
   164  		// Cap the size of the wait buffer.
   165  		if copyLen > room {
   166  			copyLen = room
   167  			src = src.TakeFirst64(room)
   168  		}
   169  		buf := make([]byte, copyLen)
   170  
   171  		// Copy the data into the wait buffer.
   172  		dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf))
   173  		n, err := safemem.CopySeq(dst, src)
   174  		if err != nil {
   175  			return 0, err
   176  		}
   177  		q.waitBufAppend(buf)
   178  
   179  		return n, nil
   180  	}))
   181  	if err != nil {
   182  		return 0, false, err
   183  	}
   184  
   185  	// Push data from the wait to the read buffer.
   186  	_, notifyEcho := q.pushWaitBufLocked(l)
   187  
   188  	return n, notifyEcho, nil
   189  }
   190  
   191  // writeBytes writes to q from b.
   192  // The returned boolean indicates whether any data was echoed back.
   193  //
   194  // Preconditions: l.termiosMu must be held for reading.
   195  func (q *queue) writeBytes(b []byte, l *lineDiscipline) bool {
   196  	q.mu.Lock()
   197  	defer q.mu.Unlock()
   198  
   199  	// Write to the wait buffer.
   200  	q.waitBufAppend(b)
   201  	_, notifyEcho := q.pushWaitBufLocked(l)
   202  	return notifyEcho
   203  }
   204  
   205  // pushWaitBufLocked fills the queue's read buffer with data from the wait
   206  // buffer.
   207  // The returned boolean indicates whether any data was echoed back.
   208  //
   209  // Preconditions:
   210  //   - l.termiosMu must be held for reading.
   211  //   - q.mu must be locked.
   212  func (q *queue) pushWaitBufLocked(l *lineDiscipline) (int, bool) {
   213  	if q.waitBufLen == 0 {
   214  		return 0, false
   215  	}
   216  
   217  	// Move data from the wait to the read buffer.
   218  	var total int
   219  	var i int
   220  	var notifyEcho bool
   221  	for i = 0; i < len(q.waitBuf); i++ {
   222  		n, echo := q.transform(l, q, q.waitBuf[i])
   223  		total += n
   224  		notifyEcho = notifyEcho || echo
   225  		if n != len(q.waitBuf[i]) {
   226  			// The read buffer filled up without consuming the
   227  			// entire buffer.
   228  			q.waitBuf[i] = q.waitBuf[i][n:]
   229  			break
   230  		}
   231  	}
   232  
   233  	// Update wait buffer based on consumed data.
   234  	q.waitBuf = q.waitBuf[i:]
   235  	q.waitBufLen -= uint64(total)
   236  
   237  	return total, notifyEcho
   238  }
   239  
   240  // Precondition: q.mu must be locked.
   241  func (q *queue) waitBufAppend(b []byte) {
   242  	q.waitBuf = append(q.waitBuf, b)
   243  	q.waitBufLen += uint64(len(b))
   244  }