fix(spec): isolate __complete probe from the controlling terminal (#111)
run the `__complete` probe subprocess in its own session so that a cobra-incompatible binary that ignores `__complete` can't mess with the user's controlling terminal. tested on both macos and nixos. encountered the problem while trying to use [visidata](https://www.visidata.org/). --------- Co-authored-by: shemishtamesh <shemishtamail@gmail.com>
This commit is contained in:
co-authored by
shemishtamesh
parent
d669e97423
commit
0b77c9334b
+13
-1
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,6 +91,16 @@ func buildCobraCacheKey(binKey string, args []string, partial string) string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newProbeCmd builds and isolates the `__complete` probe command.
|
||||||
|
// starts the child in its own session so it has no controlling terminal
|
||||||
|
// and therefore won't affect the user's tty in the case of programs that
|
||||||
|
// don't respect `__complete`.
|
||||||
|
func newProbeCmd(ctx context.Context, binName string, args []string) *exec.Cmd {
|
||||||
|
cmd := exec.CommandContext(ctx, binName, args...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
// QueryCobraComplete calls `binName __complete <args> <partial>` and returns
|
// QueryCobraComplete calls `binName __complete <args> <partial>` and returns
|
||||||
// structured suggestions cached per binary mtime, args, and partial.
|
// structured suggestions cached per binary mtime, args, and partial.
|
||||||
// returns nil if the binary is not Cobra-based or times out.
|
// returns nil if the binary is not Cobra-based or times out.
|
||||||
@@ -113,7 +124,8 @@ func QueryCobraComplete(binName string, args []string, partial string) []Suggest
|
|||||||
|
|
||||||
cmdArgs := append([]string{"__complete"}, args...)
|
cmdArgs := append([]string{"__complete"}, args...)
|
||||||
cmdArgs = append(cmdArgs, partial)
|
cmdArgs = append(cmdArgs, partial)
|
||||||
out, err := exec.CommandContext(ctx, binName, cmdArgs...).Output()
|
probe := newProbeCmd(ctx, binName, cmdArgs)
|
||||||
|
out, err := probe.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package spec
|
package spec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseCobraOutput_ValidCobra(t *testing.T) {
|
func TestParseCobraOutput_ValidCobra(t *testing.T) {
|
||||||
@@ -131,6 +137,35 @@ func TestLookup_CobraKubectl(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewProbeCmd_Setsid(t *testing.T) {
|
||||||
|
cmd := newProbeCmd(context.Background(), "true", nil)
|
||||||
|
if cmd.SysProcAttr == nil || !cmd.SysProcAttr.Setsid {
|
||||||
|
t.Fatalf("expected probe command to set Setsid: true, got %+v", cmd.SysProcAttr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNewProbeCmd_NoControllingTerminal writes a small script
|
||||||
|
// and runs it to check
|
||||||
|
// if newProbeCmd actually denies it access to tty
|
||||||
|
func TestNewProbeCmd_NoControllingTerminal(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
script := "#!/bin/sh\nexec 3<>/dev/tty"
|
||||||
|
|
||||||
|
binPath := filepath.Join(dir, "ttyprobe")
|
||||||
|
if err := os.WriteFile(binPath, []byte(script), 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to write probe script: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
runErr := newProbeCmd(ctx, binPath, nil).Run()
|
||||||
|
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if !errors.As(runErr, &exitErr) {
|
||||||
|
t.Fatalf("expected probe script to exit nonzero after failing to open /dev/tty, got %v", runErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLookup_CobraGolangciLint(t *testing.T) {
|
func TestLookup_CobraGolangciLint(t *testing.T) {
|
||||||
results := Lookup("golangci-lint ")
|
results := Lookup("golangci-lint ")
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user