diff --git a/pkg/sentry/fsimpl/devpts/devpts_test.go b/pkg/sentry/fsimpl/devpts/devpts_test.go index 5b7cb9671..09a4d1f99 100644 --- a/pkg/sentry/fsimpl/devpts/devpts_test.go +++ b/pkg/sentry/fsimpl/devpts/devpts_test.go @@ -82,3 +82,54 @@ func TestEchoDeadlock(t *testing.T) { t.Fatalf("written and read strings do not match: got %q, want %q", outStr, inStr) } } + +func TestEndOfFileHandling(t *testing.T) { + ctx := contexttest.Context(t) + termios := linux.DefaultReplicaTermios + ld := newLineDiscipline(termios, nil) + + // EOF with non-empty read buffer. + inBytes := []byte("hello, tty") + inBytes = append(inBytes, termios.ControlCharacters[linux.VEOF]) + outBytes := make([]byte, 32) + dst := usermem.BytesIOSequence(outBytes) + // Write to the input queue. + nw, err := ld.inputQueueWrite(ctx, usermem.BytesIOSequence(inBytes)) + if err != nil { + t.Fatalf("error writing to input queue: %v", err) + } + if nw != int64(len(inBytes)) { + t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes)) + } + + // Read from the input queue. + nr, err := ld.inputQueueRead(ctx, dst) + if err != nil { + t.Fatalf("error reading from input queue: %v", err) + } + if nr != int64(len(inBytes)-1) { + t.Fatalf("read wrong length: got %d, want %d", nr, len(inBytes)-1) + } + + // EOF with empty read buffer. + inBytes = []byte{termios.ControlCharacters[linux.VEOF]} + outBytes = make([]byte, 32) + dst = usermem.BytesIOSequence(outBytes) + // Write to the input queue. + nw, err = ld.inputQueueWrite(ctx, usermem.BytesIOSequence(inBytes)) + if err != nil { + t.Fatalf("error writing to input queue: %v", err) + } + if nw != int64(len(inBytes)) { + t.Fatalf("wrote wrong length: got %d, want %d", nw, len(inBytes)) + } + + // Read from the input queue. + nr, err = ld.inputQueueRead(ctx, dst) + if err != nil { + t.Fatalf("error reading from input queue: %v", err) + } + if nr != 0 { + t.Fatalf("read length should be zero: got %d", nr) + } +} diff --git a/pkg/sentry/fsimpl/devpts/line_discipline.go b/pkg/sentry/fsimpl/devpts/line_discipline.go index 20d68f75e..018cb930a 100644 --- a/pkg/sentry/fsimpl/devpts/line_discipline.go +++ b/pkg/sentry/fsimpl/devpts/line_discipline.go @@ -198,6 +198,7 @@ func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { l.termiosMu.RLock() n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l) + isCanon := l.termios.LEnabled(linux.ICANON) l.termiosMu.RUnlock() if err != nil { return 0, err @@ -212,9 +213,14 @@ func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSeque l.replicaWaiter.Notify(waiter.ReadableEvents) } return n, nil - } else if notifyEcho { + } + if notifyEcho { l.masterWaiter.Notify(waiter.ReadableEvents) } + if !pushed && isCanon { + return 0, nil // EOF + } + return 0, linuxerr.ErrWouldBlock } diff --git a/pkg/sentry/fsimpl/devpts/queue.go b/pkg/sentry/fsimpl/devpts/queue.go index affb90601..877c2f58d 100644 --- a/pkg/sentry/fsimpl/devpts/queue.go +++ b/pkg/sentry/fsimpl/devpts/queue.go @@ -36,7 +36,7 @@ const waitBufMaxBytes = 131072 // full, at which point they are written to the wait buffer. Bytes are // processed (i.e. undergo termios transformations) as they are added to the // read buffer. The read buffer is readable when its length is nonzero and -// readable is true. +// readable is true, or when its length is zero and readable is true (EOF). // // +stateify savable type queue struct {