我正在实现ssh服务器的特性,因此给定shell请求,我打开一个ty-tty对。
代码片段:
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
}
}
}
一旦ssh通道关闭,我就关闭该通道。我期望的行为是它应该发送SIGHUP到shell。
如果我注释掉(1)
副本(src: myty, dst: channel),它就可以工作了!
然而-当它没有被注释掉时:
(1)
副本不返回,意味着mypty
的read
系统调用仍然阻塞,并且不返回eof =>主设备没有关闭?- shell没有得到SIGHUP
我不知道为什么如果我注释出(1)
拷贝它的工作,也许内核参考计数读取?
我的领导:
- 企业。Read实际上被分派给tty,如下所示:主机缺少读功能
- SIGHUP流程演练
drivers/tty/pty.c
中的pty_close
,它调用tty_vhangup(tty->link);
,见这里- Linux设备驱动程序,第三版,PTY章节
指出:
我直接关闭fd,因为否则使用通常的
中保持打开状态os.File.close()
不会因为某种原因关闭fd,它在/proc/<pid>/fd
用直接的
read
系统调用替换(1)
副本将导致相同的结果
谢谢!
我自己也遇到了这个问题,并花了一些时间进行调查。
证明这是Go运行时的故意行为。具体来说,它可以防止文件在被另一个程序读取时被关闭。这是为了缓解一种竞争条件,即如果文件描述符被关闭并被另一个运行例程重用,则运行例程可能从错误的文件中读取。
对于SSHD用例,我没有一个好的解决方案。稍后我可能会更新这个答案。你有自己的解决办法吗?