Skip to content

Commit be27fcf

Browse files
ianlancetaylorgopherbot
authored andcommitted
os, internal/poll: don't use splice with tty
Also don't try to wait for a non-pollable FD. Fixes #59041 Change-Id: Ife469d8738f2cc27c0beba223bdc8f8bc757b2a7 Reviewed-on: https://go-review.googlesource.com/c/go/+/476335 Run-TryBot: Ian Lance Taylor <[email protected]> Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 617cf13 commit be27fcf

File tree

3 files changed

+107
-9
lines changed

3 files changed

+107
-9
lines changed

src/internal/poll/splice_linux.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import (
1212
)
1313

1414
const (
15-
// spliceNonblock makes calls to splice(2) non-blocking.
16-
spliceNonblock = 0x2
17-
1815
// maxSpliceSize is the maximum amount of data Splice asks
1916
// the kernel to move in a single call to splice(2).
2017
// We use 1MB as Splice writes data through a pipe, and 1MB is the default maximum pipe buffer size,
@@ -91,15 +88,17 @@ func spliceDrain(pipefd int, sock *FD, max int) (int, error) {
9188
return 0, err
9289
}
9390
for {
94-
n, err := splice(pipefd, sock.Sysfd, max, spliceNonblock)
91+
n, err := splice(pipefd, sock.Sysfd, max, 0)
9592
if err == syscall.EINTR {
9693
continue
9794
}
9895
if err != syscall.EAGAIN {
9996
return n, err
10097
}
101-
if err := sock.pd.waitRead(sock.isFile); err != nil {
102-
return n, err
98+
if sock.pd.pollable() {
99+
if err := sock.pd.waitRead(sock.isFile); err != nil {
100+
return n, err
101+
}
103102
}
104103
}
105104
}
@@ -127,7 +126,7 @@ func splicePump(sock *FD, pipefd int, inPipe int) (int, error) {
127126
}
128127
written := 0
129128
for inPipe > 0 {
130-
n, err := splice(sock.Sysfd, pipefd, inPipe, spliceNonblock)
129+
n, err := splice(sock.Sysfd, pipefd, inPipe, 0)
131130
// Here, the condition n == 0 && err == nil should never be
132131
// observed, since Splice controls the write side of the pipe.
133132
if n > 0 {
@@ -138,8 +137,10 @@ func splicePump(sock *FD, pipefd int, inPipe int) (int, error) {
138137
if err != syscall.EAGAIN {
139138
return written, err
140139
}
141-
if err := sock.pd.waitWrite(sock.isFile); err != nil {
142-
return written, err
140+
if sock.pd.pollable() {
141+
if err := sock.pd.waitWrite(sock.isFile); err != nil {
142+
return written, err
143+
}
143144
}
144145
}
145146
return written, nil

src/os/readfrom_linux.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ func (f *File) readFrom(r io.Reader) (written int64, handled bool, err error) {
3333
}
3434

3535
func (f *File) spliceToFile(r io.Reader) (written int64, handled bool, err error) {
36+
// At least as of kernel 5.19.11, splice to a tty fails.
37+
// poll.Splice will do the wrong thing if it can splice from r
38+
// but can't splice to f: it will read data from r, which is
39+
// not what we want if r is a pipe or socket.
40+
// So we have to check now whether f is a tty.
41+
fi, err := f.Stat()
42+
if err != nil {
43+
return 0, false, err
44+
}
45+
if fi.Mode()&ModeCharDevice != 0 {
46+
return 0, false, nil
47+
}
48+
3649
var (
3750
remain int64
3851
lr *io.LimitedReader

src/os/readfrom_linux_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ package os_test
66

77
import (
88
"bytes"
9+
"errors"
910
"internal/poll"
11+
"internal/testpty"
1012
"io"
1113
"math/rand"
1214
"net"
@@ -16,6 +18,7 @@ import (
1618
"runtime"
1719
"strconv"
1820
"strings"
21+
"sync"
1922
"syscall"
2023
"testing"
2124
"time"
@@ -279,6 +282,12 @@ func TestSpliceFile(t *testing.T) {
279282
})
280283
}
281284
})
285+
t.Run("TCP-To-TTY", func(t *testing.T) {
286+
testSpliceToTTY(t, "tcp", 32768)
287+
})
288+
t.Run("Unix-To-TTY", func(t *testing.T) {
289+
testSpliceToTTY(t, "unix", 32768)
290+
})
282291
t.Run("Limited", func(t *testing.T) {
283292
t.Run("OneLess-TCP", func(t *testing.T) {
284293
for _, size := range sizes {
@@ -396,6 +405,81 @@ func testSpliceFile(t *testing.T, proto string, size, limit int64) {
396405
}
397406
}
398407

408+
// Issue #59041.
409+
func testSpliceToTTY(t *testing.T, proto string, size int64) {
410+
var wg sync.WaitGroup
411+
412+
// Call wg.Wait as the final deferred function,
413+
// because the goroutines may block until some of
414+
// the deferred Close calls.
415+
defer wg.Wait()
416+
417+
pty, ttyName, err := testpty.Open()
418+
if err != nil {
419+
t.Skipf("skipping test because pty open failed: %v", err)
420+
}
421+
defer pty.Close()
422+
423+
// Open the tty directly, rather than via OpenFile.
424+
// This bypasses the non-blocking support and is required
425+
// to recreate the problem in the issue (#59041).
426+
ttyFD, err := syscall.Open(ttyName, syscall.O_RDWR, 0)
427+
if err != nil {
428+
t.Skipf("skipping test becaused failed to open tty: %v", err)
429+
}
430+
defer syscall.Close(ttyFD)
431+
432+
tty := NewFile(uintptr(ttyFD), "tty")
433+
defer tty.Close()
434+
435+
client, server := createSocketPair(t, proto)
436+
437+
data := bytes.Repeat([]byte{'a'}, int(size))
438+
439+
wg.Add(1)
440+
go func() {
441+
defer wg.Done()
442+
// The problem (issue #59041) occurs when writing
443+
// a series of blocks of data. It does not occur
444+
// when all the data is written at once.
445+
for i := 0; i < len(data); i += 1024 {
446+
if _, err := client.Write(data[i : i+1024]); err != nil {
447+
// If we get here because the client was
448+
// closed, skip the error.
449+
if !errors.Is(err, net.ErrClosed) {
450+
t.Errorf("error writing to socket: %v", err)
451+
}
452+
return
453+
}
454+
}
455+
client.Close()
456+
}()
457+
458+
wg.Add(1)
459+
go func() {
460+
defer wg.Done()
461+
buf := make([]byte, 32)
462+
for {
463+
if _, err := pty.Read(buf); err != nil {
464+
if err != io.EOF && !errors.Is(err, ErrClosed) {
465+
// An error here doesn't matter for
466+
// our test.
467+
t.Logf("error reading from pty: %v", err)
468+
}
469+
return
470+
}
471+
}
472+
}()
473+
474+
// Close Client to wake up the writing goroutine if necessary.
475+
defer client.Close()
476+
477+
_, err = io.Copy(tty, server)
478+
if err != nil {
479+
t.Fatal(err)
480+
}
481+
}
482+
399483
func testCopyFileRange(t *testing.T, size int64, limit int64) {
400484
dst, src, data, hook := newCopyFileRangeTest(t, size)
401485

0 commit comments

Comments
 (0)