mirror of
https://github.com/fosrl/newt.git
synced 2026-10-06 20:59:07 +02:00
Fix exit not closing session
This commit is contained in:
+30
-3
@@ -1,9 +1,11 @@
|
|||||||
package nativessh
|
package nativessh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
)
|
)
|
||||||
@@ -11,8 +13,10 @@ import (
|
|||||||
// PTYSession is a running shell process attached to a PTY.
|
// PTYSession is a running shell process attached to a PTY.
|
||||||
// It implements io.ReadWriteCloser so it can be bridged to any transport.
|
// It implements io.ReadWriteCloser so it can be bridged to any transport.
|
||||||
type PTYSession struct {
|
type PTYSession struct {
|
||||||
ptmx *os.File
|
ptmx *os.File
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
waitOnce sync.Once
|
||||||
|
exitCode int
|
||||||
}
|
}
|
||||||
|
|
||||||
// findShell returns the path to the best available interactive shell by
|
// findShell returns the path to the best available interactive shell by
|
||||||
@@ -54,9 +58,32 @@ func (p *PTYSession) Resize(cols, rows uint16) error {
|
|||||||
return pty.Setsize(p.ptmx, &pty.Winsize{Cols: cols, Rows: rows})
|
return pty.Setsize(p.ptmx, &pty.Winsize{Cols: cols, Rows: rows})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait waits for the child process to exit exactly once and records its exit
|
||||||
|
// code. Safe to call concurrently or multiple times.
|
||||||
|
func (p *PTYSession) wait() {
|
||||||
|
p.waitOnce.Do(func() {
|
||||||
|
err := p.cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
p.exitCode = exitErr.ExitCode()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.exitCode = 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExitCode waits for the shell process to exit and returns its exit code.
|
||||||
|
// It is safe to call before or after Close.
|
||||||
|
func (p *PTYSession) ExitCode() int {
|
||||||
|
p.wait()
|
||||||
|
return p.exitCode
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the PTY and waits for the child process to exit.
|
// Close closes the PTY and waits for the child process to exit.
|
||||||
func (p *PTYSession) Close() error {
|
func (p *PTYSession) Close() error {
|
||||||
err := p.ptmx.Close()
|
err := p.ptmx.Close()
|
||||||
_ = p.cmd.Wait()
|
p.wait()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,8 +177,18 @@ func (s *Server) handleSession(ch ssh.Channel, requests <-chan *ssh.Request) {
|
|||||||
// PTY output → SSH channel.
|
// PTY output → SSH channel.
|
||||||
go func() {
|
go func() {
|
||||||
_, _ = io.Copy(ch, sess)
|
_, _ = io.Copy(ch, sess)
|
||||||
|
// Notify the client of the shell's exit status so it can
|
||||||
|
// disconnect cleanly instead of requiring a manual disconnect.
|
||||||
|
exitCode := sess.ExitCode()
|
||||||
|
exitStatusPayload := ssh.Marshal(struct{ Status uint32 }{uint32(exitCode)})
|
||||||
|
_, _ = ch.SendRequest("exit-status", false, exitStatusPayload)
|
||||||
_ = ch.CloseWrite()
|
_ = ch.CloseWrite()
|
||||||
sess.Close() //nolint:errcheck
|
sess.Close() //nolint:errcheck
|
||||||
|
// Close the channel so the ssh library closes the requests
|
||||||
|
// channel, which unblocks the for-range loop in handleSession
|
||||||
|
// and allows the deferred ch.Close() to run. Without this,
|
||||||
|
// handleSession blocks forever waiting for requests to drain.
|
||||||
|
_ = ch.Close()
|
||||||
}()
|
}()
|
||||||
// SSH channel input → PTY stdin.
|
// SSH channel input → PTY stdin.
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
Reference in New Issue
Block a user