I'm implementing features of an ssh server, so given a shell request I open a pty-tty pair.
A snippet:
import (
"github.com/creack/pty"
...
)
func attachPty(channel ssh.Channel, shell *exec.Cmd) {
mypty, err := pty.Start(shell)
go func() {
io.Copy(channel, mypty) // (1) ; could also be substituted with read() syscall, same problem
}
go func() {
io.Copy(mypty, channel) // (2) - this returns on channel exit with eof, so let's close mypty
if err := syscall.Close(int(mypty.Fd())); err != nil {
fmt.Printf("error closing fd") // no error is printed out, /proc/fd shows it's successfuly closed
}
}
}
Once the ssh channel gets closed, I close the pty. My expected behavior is that it should send SIGHUP to the shell.
If I comment out the (1)
copy (src: mypty, dst: channel), it works!
However - when it's not commented out:
- The
(1)
copy doesn't return, meaning theread
syscall frommypty
is still blocking, and doesn't return eof => master device doesn't get closed? - shell doesn't get SIGHUP
I'm not sure why if I comment out the (1)
copy it works, maybe the kernel reference counts the reads?
My leads:
- pty.read is actually dispatched to the tty, as said in: pty master missing read function
- Walkthrough of SIGHUP flow
pty_close
indrivers/tty/pty.c
, which callstty_vhangup(tty->link);
, see here- Linux Device Drivers, 3rd edition, PTY chapter
Go notes:
I close the fd directly, because otherwise using the usual
os.File.close()
doesn't actually close the fd for some reason, it stays open in/proc/<pid>/fd
substituting the
(1)
copy with a directread
syscall would lead to the same outcome
Thank you!