fix panic: send on closed channel

on windows: if the term is resized when ReadString is being called and no one is reading form ws, we get a panic: send on closed channel.
It could make sense to poll getconsolescreenbufferinfo calls instead of this:
something started from the sigwinch() func

on unix: same story, sigwinch was listened upon by default but if the
signals (chan sends) were not handled, this could cause a crash

fix #7434
This commit is contained in:
Adrien Delorme 2019-04-18 13:20:41 +02:00
parent fa7b922e7a
commit 71c00c200f
7 changed files with 84 additions and 35 deletions

2
go.mod
View File

@ -68,7 +68,7 @@ require (
github.com/masterzen/azure-sdk-for-go v0.0.0-20161014135628-ee4f0065d00c // indirect
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect
github.com/masterzen/winrm v0.0.0-20180224160350-7e40f93ae939
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859
github.com/miekg/dns v1.1.1 // indirect
github.com/mitchellh/cli v1.0.0
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7

4
go.sum
View File

@ -231,8 +231,8 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007 h1:xjZxmVDmDZoEsl2gV0qD0pyBH+wXmJIZd27wsNFphJk=
github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859 h1:smQbSzmT3EHl4EUwtFwFGmGIpiYgIiiPeVv1uguIQEE=
github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=

View File

@ -38,6 +38,10 @@ func (tty *TTY) Size() (int, int, error) {
return tty.size()
}
func (tty *TTY) SizePixel() (int, int, int, int, error) {
return tty.sizePixel()
}
func (tty *TTY) Input() *os.File {
return tty.input()
}
@ -115,6 +119,6 @@ type WINSIZE struct {
H int
}
func (tty *TTY) SIGWINCH() chan WINSIZE {
func (tty *TTY) SIGWINCH() <-chan WINSIZE {
return tty.sigwinch()
}

View File

@ -4,6 +4,7 @@ import (
"bufio"
"os"
"syscall"
"errors"
)
type TTY struct {
@ -54,6 +55,11 @@ func (tty *TTY) size() (int, int, error) {
return 80, 24, nil
}
func (tty *TTY) sizePixel() (int, int, int, int, error) {
x, y, _ := tty.size()
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Plan 9")
}
func (tty *TTY) input() *os.File {
return tty.in
}

View File

@ -18,7 +18,6 @@ type TTY struct {
bin *bufio.Reader
out *os.File
termios syscall.Termios
ws chan WINSIZE
ss chan os.Signal
}
@ -48,24 +47,8 @@ func open() (*TTY, error) {
return nil, err
}
tty.ws = make(chan WINSIZE)
tty.ss = make(chan os.Signal, 1)
signal.Notify(tty.ss, syscall.SIGWINCH)
go func() {
defer close(tty.ws)
for sig := range tty.ss {
switch sig {
case syscall.SIGWINCH:
if w, h, err := tty.size(); err == nil {
tty.ws <- WINSIZE{
W: w,
H: h,
}
}
default:
}
}
}()
return tty, nil
}
@ -86,11 +69,16 @@ func (tty *TTY) close() error {
}
func (tty *TTY) size() (int, int, error) {
x, y, _, _, err := tty.sizePixel()
return x, y, err
}
func (tty *TTY) sizePixel() (int, int, int, int, error) {
var dim [4]uint16
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(tty.out.Fd()), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dim)), 0, 0, 0); err != 0 {
return -1, -1, err
return -1, -1, -1, -1, err
}
return int(dim[1]), int(dim[0]), nil
return int(dim[1]), int(dim[0]), int(dim[2]), int(dim[3]), nil
}
func (tty *TTY) input() *os.File {
@ -127,6 +115,28 @@ func (tty *TTY) raw() (func() error, error) {
}, nil
}
func (tty *TTY) sigwinch() chan WINSIZE {
return tty.ws
func (tty *TTY) sigwinch() <-chan WINSIZE {
signal.Notify(tty.ss, syscall.SIGWINCH)
ws := make(chan WINSIZE)
go func() {
defer close(ws)
for sig := range tty.ss {
if sig != syscall.SIGWINCH {
continue
}
w, h, err := tty.size()
if err != nil {
continue
}
// send but do not block for it
select {
case ws <- WINSIZE{W: w, H: h}:
default:
}
}
}()
return ws
}

View File

@ -3,7 +3,9 @@
package tty
import (
"context"
"os"
"errors"
"syscall"
"unsafe"
@ -122,11 +124,13 @@ type charInfo struct {
}
type TTY struct {
in *os.File
out *os.File
st uint32
rs []rune
ws chan WINSIZE
in *os.File
out *os.File
st uint32
rs []rune
ws chan WINSIZE
sigwinchCtx context.Context
sigwinchCtxCancel context.CancelFunc
}
func readConsoleInput(fd uintptr, record *inputRecord) (err error) {
@ -183,6 +187,7 @@ func open() (*TTY, error) {
procSetConsoleMode.Call(h, uintptr(st))
tty.ws = make(chan WINSIZE)
tty.sigwinchCtx, tty.sigwinchCtxCancel = context.WithCancel(context.Background())
return tty, nil
}
@ -206,10 +211,24 @@ func (tty *TTY) readRune() (rune, error) {
switch ir.eventType {
case windowBufferSizeEvent:
wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event))
tty.ws <- WINSIZE{
ws := WINSIZE{
W: int(wr.size.x),
H: int(wr.size.y),
}
if err := tty.sigwinchCtx.Err(); err != nil {
// closing
// the following select might panic without this guard close
return 0, err
}
select {
case tty.ws <- ws:
case <-tty.sigwinchCtx.Done():
return 0, tty.sigwinchCtx.Err()
default:
return 0, nil // no one is currently trying to read
}
case keyEvent:
kr := (*keyEventRecord)(unsafe.Pointer(&ir.event))
if kr.keyDown != 0 {
@ -307,8 +326,9 @@ func (tty *TTY) readRune() (rune, error) {
}
func (tty *TTY) close() error {
close(tty.ws)
procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st))
tty.sigwinchCtxCancel()
close(tty.ws)
return nil
}
@ -321,6 +341,15 @@ func (tty *TTY) size() (int, int, error) {
return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
}
func (tty *TTY) sizePixel() (int, int, int, int, error) {
x, y, err := tty.size()
if err != nil {
x = -1
y = -1
}
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Windows")
}
func (tty *TTY) input() *os.File {
return tty.in
}
@ -349,6 +378,6 @@ func (tty *TTY) raw() (func() error, error) {
}, nil
}
func (tty *TTY) sigwinch() chan WINSIZE {
func (tty *TTY) sigwinch() <-chan WINSIZE {
return tty.ws
}

2
vendor/modules.txt vendored
View File

@ -299,7 +299,7 @@ github.com/mattn/go-colorable
github.com/mattn/go-isatty
# github.com/mattn/go-runewidth v0.0.4
github.com/mattn/go-runewidth
# github.com/mattn/go-tty v0.0.0-20190407112021-83fae09cc007
# github.com/mattn/go-tty v0.0.0-20190424173100-523744f04859
github.com/mattn/go-tty
# github.com/mitchellh/cli v1.0.0
github.com/mitchellh/cli