Fix PTY workdir and process group teardown
This commit is contained in:
@@ -228,7 +228,7 @@ func (c *Child) startPTY(cols, rows uint16) (uint64, error) {
|
|||||||
}
|
}
|
||||||
starting := StatusStarting
|
starting := StatusStarting
|
||||||
c.status.Store(&starting)
|
c.status.Store(&starting)
|
||||||
p, err := pkgpty.Start(c.Argv, c.Env, cols, rows)
|
p, err := pkgpty.Start(c.Argv, c.Env, c.WorkDir, cols, rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
em.Close()
|
em.Close()
|
||||||
errored := StatusErrored
|
errored := StatusErrored
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
cpty "github.com/creack/pty"
|
cpty "github.com/creack/pty"
|
||||||
)
|
)
|
||||||
@@ -19,11 +20,13 @@ type PTY struct {
|
|||||||
// Start spawns argv with stdin/stdout/stderr attached to a new PTY sized
|
// Start spawns argv with stdin/stdout/stderr attached to a new PTY sized
|
||||||
// (cols, rows). The returned PTY exposes the master fd for the parent to
|
// (cols, rows). The returned PTY exposes the master fd for the parent to
|
||||||
// read from and write to.
|
// read from and write to.
|
||||||
func Start(argv []string, env []string, cols, rows uint16) (*PTY, error) {
|
func Start(argv []string, env []string, workDir string, cols, rows uint16) (*PTY, error) {
|
||||||
if len(argv) == 0 {
|
if len(argv) == 0 {
|
||||||
return nil, fmt.Errorf("pty: empty argv")
|
return nil, fmt.Errorf("pty: empty argv")
|
||||||
}
|
}
|
||||||
cmd := exec.Command(argv[0], argv[1:]...)
|
cmd := exec.Command(argv[0], argv[1:]...)
|
||||||
|
cmd.Dir = workDir
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true}
|
||||||
if env != nil {
|
if env != nil {
|
||||||
cmd.Env = ensureTerm(env)
|
cmd.Env = ensureTerm(env)
|
||||||
} else {
|
} else {
|
||||||
@@ -88,6 +91,10 @@ func (p *PTY) Close() error {
|
|||||||
p.master = nil
|
p.master = nil
|
||||||
}
|
}
|
||||||
if p.cmd != nil && p.cmd.Process != nil {
|
if p.cmd != nil && p.cmd.Process != nil {
|
||||||
|
pid := p.cmd.Process.Pid
|
||||||
|
if pid > 0 {
|
||||||
|
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||||
|
}
|
||||||
_ = p.cmd.Process.Kill()
|
_ = p.cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
return firstErr
|
return firstErr
|
||||||
|
|||||||
84
internal/pty/pty_test.go
Normal file
84
internal/pty/pty_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStartUsesWorkDir(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
p, err := Start([]string{"sh", "-c", "pwd"}, nil, dir, 80, 24)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Start: %v", err)
|
||||||
|
}
|
||||||
|
defer p.Close()
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
buf := make([]byte, 256)
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
n, err := p.Read(buf)
|
||||||
|
if n > 0 {
|
||||||
|
out.Write(buf[:n])
|
||||||
|
if strings.Contains(out.String(), dir) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = p.Wait()
|
||||||
|
|
||||||
|
if got := strings.TrimSpace(out.String()); got != dir {
|
||||||
|
t.Fatalf("pwd output = %q, want %q", got, dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseKillsProcessGroup(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
pidFile := filepath.Join(dir, "sleep.pid")
|
||||||
|
env := append(os.Environ(), "PIDFILE="+pidFile)
|
||||||
|
p, err := Start([]string{"sh", "-c", "sleep 30 & echo $! > \"$PIDFILE\"; wait"}, env, "", 80, 24)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Start: %v", err)
|
||||||
|
}
|
||||||
|
deadline := time.Now().Add(5 * time.Second)
|
||||||
|
var childPID int
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
b, err := os.ReadFile(pidFile)
|
||||||
|
if err == nil {
|
||||||
|
childPID, _ = strconv.Atoi(strings.TrimSpace(string(b)))
|
||||||
|
if childPID > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
if childPID <= 0 {
|
||||||
|
_ = p.Close()
|
||||||
|
t.Fatalf("background child pid was not written")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Close(); err != nil {
|
||||||
|
t.Fatalf("Close: %v", err)
|
||||||
|
}
|
||||||
|
_ = p.Wait()
|
||||||
|
|
||||||
|
deadline = time.Now().Add(5 * time.Second)
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
err := syscall.Kill(childPID, 0)
|
||||||
|
if errors.Is(err, syscall.ESRCH) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(20 * time.Millisecond)
|
||||||
|
}
|
||||||
|
t.Fatalf("background child pid %d still exists after PTY.Close", childPID)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user