session: signal owners that predate the kill frame
9 unmodified lines10111213141516171815 unmodified lines343536373738399 unmodified lines github.com/gitpod-io/xterm-go v0.0.0-20260907130418-dae5128cb6b3 github.com/klauspost/compress v1.19.1 github.com/sahilm/fuzzy v0.1.3 github.com/samber/lo v1.53.0 golang.org/x/sys v0.47.0 golang.org/x/term v0.45.0)
require (15 unmodified lines github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.40.0 // indirect)1234567891011package session
import "golang.org/x/sys/unix"
func sockPeerPID(fd int) int { pid, err := unix.GetsockoptInt(fd, unix.SOL_LOCAL, unix.LOCAL_PEERPID) if err != nil { return 0 } return pid}1234567891011package session
import "golang.org/x/sys/unix"
func sockPeerPID(fd int) int { cred, err := unix.GetsockoptUcred(fd, unix.SOL_SOCKET, unix.SO_PEERCRED) if err != nil { return 0 } return int(cred.Pid)}12345//go:build !linux && !darwin
package session
func sockPeerPID(int) int { return 0 }247 unmodified linesExpand all2482492502512522522532542552561 unmodified line258259260261262263264265266266267268269270271272273274269275276277278279280281282283284285286287288289271290273291292293294295296297298299300301302303304305306307308309310311415 unmodified linesExpand all247 unmodified linesExpand all
return protocol.WriteFrame(conn, protocol.TypeHop, []byte(target))}
// an owner that predates TypeKill just hangs up, so a socket that still answers means it lived// an owner that predates TypeKill just hangs up, so one that still answers gets a signal insteadfunc Kill(id string) error { sock := SocketPath(id) if sock == "" { return fmt.Errorf("invalid session id: %q", id)1 unmodified line conn, err := net.DialTimeout("unix", sock, 250*time.Millisecond) if err != nil { return fmt.Errorf("session %q is not running", id) } pid := peerPID(conn) _ = conn.SetDeadline(time.Now().Add(2 * time.Second)) err = protocol.WriteFrame(conn, protocol.TypeKill, nil) _, _ = io.Copy(io.Discard, conn) _ = conn.Close() if err != nil { switch { case err != nil: return err case !answers(sock): return nil case pid <= 0: return fmt.Errorf("session %q outlived the kill and its owner pid is unknown", id) } if c, err := net.DialTimeout("unix", sock, 250*time.Millisecond); err == nil { if err := syscall.Kill(pid, syscall.SIGTERM); err != nil { return fmt.Errorf("signal the owner of %q: %w", id, err) } for deadline := time.Now().Add(time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) { if !answers(sock) { return nil } } return fmt.Errorf("session %q outlived SIGTERM", id)}
func answers(sock string) bool { c, err := net.DialTimeout("unix", sock, 250*time.Millisecond) if err == nil { _ = c.Close() return fmt.Errorf("session %q outlived the kill, its owner is too old to take one", id) } return nil return err == nil}
func peerPID(c net.Conn) int { uc, ok := c.(*net.UnixConn) if !ok { return 0 } raw, err := uc.SyscallConn() if err != nil { return 0 } pid := 0 _ = raw.Control(func(fd uintptr) { pid = sockPeerPID(int(fd)) }) return pid}
type outMsg struct { t protocol.FrameType p []byte}415 unmodified linesExpand all1 unmodified line234567891083 unmodified lines94959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413585 unmodified lines1 unmodified line
import ( "net" "os" "os/exec" "path/filepath" "strings" "testing" "time"83 unmodified lines t.Fatalf("snapshot lost sgr mouse: %v %q", err, snap) }}
func TestKillSignalsAnOwnerThatPredatesIt(t *testing.T) { if sock := os.Getenv("TOBI_TEST_DEAF_OWNER"); sock != "" { l, err := net.Listen("unix", sock) if err != nil { os.Exit(1) } for { c, err := l.Accept() if err != nil { os.Exit(1) } _, _, _ = protocol.ReadFrame(c) _ = c.Close() } } t.Setenv("XDG_RUNTIME_DIR", shortTempDir(t)) id := GenerateID() owner := exec.Command(os.Args[0], "-test.run=^TestKillSignalsAnOwnerThatPredatesIt$") owner.Env = append(os.Environ(), "TOBI_TEST_DEAF_OWNER="+SocketPath(id)) if err := owner.Start(); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = owner.Process.Kill() }) for i := 0; i < 100 && !answers(SocketPath(id)); i++ { time.Sleep(20 * time.Millisecond) } if err := Kill(id); err != nil { t.Fatal(err) } if err := owner.Wait(); err == nil || !strings.Contains(err.Error(), "terminated") { t.Fatalf("owner exit: %v", err) }}
func TestQueryFilter(t *testing.T) { var f queryFilter for _, tc := range []struct{ in, want string }{ {"\x1b]10", ""}, {";?\x1b\\", ""},85 unmodified lines